diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 19ce453b6..feb0d24c3 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -4,6 +4,11 @@ This is a pre-release version and APIs will change quickly. Before `1.0` release Please note after `1.0` Semver will be followed using normal protocols. +# Version 0.3.0 + +### API Changes +* All settings now permit attributes with either kebab or lowercased conversion. i.e. settings = { showLink: false } can be set either like `` or `` as an alias. + # Version 0.2.2 * UI: Adds new UI card component with minimal featureset * Bugfix: Fixed issue with SSR `` being rendered as `` diff --git a/docs/src/components/CodePlayground/CodePlayground.js b/docs/src/components/CodePlayground/CodePlayground.js index 25437f2db..4ce1e73eb 100644 --- a/docs/src/components/CodePlayground/CodePlayground.js +++ b/docs/src/components/CodePlayground/CodePlayground.js @@ -422,7 +422,7 @@ const keys = { }; const events = { - 'global resize window'({state}) { + 'global resize window'({self, state}) { requestAnimationFrame(self.setDisplayMode); }, 'change ui-menu.files'({state, data}) { diff --git a/docs/src/components/MobileMenu/MobileMenu.html b/docs/src/components/MobileMenu/MobileMenu.html index 084df9a62..df2a1c67a 100644 --- a/docs/src/components/MobileMenu/MobileMenu.html +++ b/docs/src/components/MobileMenu/MobileMenu.html @@ -5,7 +5,7 @@
@@ -15,7 +15,7 @@ {#if hasAny previousMenu.menu}
@@ -31,7 +31,7 @@
diff --git a/docs/src/components/Sidebar.astro b/docs/src/components/Sidebar.astro index b3e793336..97504b5ba 100644 --- a/docs/src/components/Sidebar.astro +++ b/docs/src/components/Sidebar.astro @@ -14,8 +14,8 @@ const canExpand = true; client:load menu={menu} use-accordion=true - expand-all={expandAll} - active-url={Astro.url.pathname} + expand-all={expandAll || false} + activeurl={Astro.url.pathname} /> diff --git a/docs/src/components/Test/component.css b/docs/src/components/Test/component.css index 928a97af9..e69de29bb 100644 --- a/docs/src/components/Test/component.css +++ b/docs/src/components/Test/component.css @@ -1,11 +0,0 @@ -table { - border-collapse: collapse; - width: 100%; -} -th { - font-weight: var(--bold); - color: var(--white); -} -th { - padding: 5px 8px; -} diff --git a/docs/src/components/Test/component.html b/docs/src/components/Test/component.html index c92c6911a..b30b5ff66 100644 --- a/docs/src/components/Test/component.html +++ b/docs/src/components/Test/component.html @@ -1,27 +1 @@ -

Hello World

-{#each header in headers} - {header} -{/each} -{stringify rowTemplate} -{log headers} -
- {#each row in rows} -
- {log rowTemplate} - {> template - name=rowTemplate - data=row - } -
- {/each} -
-

Second

-{> template - name=rowTemplate - data={ firstName: 'johnny', lastName: 'fingers', age: 22} -} -

Third

- -{#each row in rows} - {stringify row} -{/each} +{firstSetting} {secondSetting} diff --git a/docs/src/components/Test/component.js b/docs/src/components/Test/component.js index 2aa8d333b..d86663be1 100644 --- a/docs/src/components/Test/component.js +++ b/docs/src/components/Test/component.js @@ -1,30 +1,24 @@ import { defineComponent } from '@semantic-ui/component'; -import { Template } from '@semantic-ui/templating'; import css from './component.css?raw'; import template from './component.html?raw'; const settings = { - rowTemplate: new Template(), // user can specify a template to render the rows - headers: [ - 'Name', - 'Age', - 'Gender', - ], - rows: [ - { firstName: 'Buck', lastName: 'Pencilsworth', age: '42', gender: 'Male' }, - ], + firstSetting: true, + secondSetting: false, }; -const createComponent = ({self, state, settings}) => ({ - getData() { - return { firstName: 'Buck', lastName: 'Pencilsworth', age: '42', gender: 'Male' }; - }, +const createComponent = ({self, data, settings}) => ({ + initialize() { + console.log('data is', data); + console.log('first', settings.firstSetting); + console.log('second', settings.secondSetting); + } }); -export const DynamicTable = defineComponent({ - tagName: 'dynamic-table', +export const SettingTest = defineComponent({ + tagName: 'setting-test', template, css, settings, diff --git a/docs/src/pages/test.astro b/docs/src/pages/test.astro index b7f1c6058..1e35de1df 100644 --- a/docs/src/pages/test.astro +++ b/docs/src/pages/test.astro @@ -1,45 +1,14 @@ --- import Body from '../layouts/Body.astro'; -import '../components/Test/component.js'; +import NavMenu from '@components/NavMenu/NavMenu.js'; +const menu = []; --- - - - + diff --git a/packages/component/src/define-component.js b/packages/component/src/define-component.js index 5c3599126..f85517c38 100644 --- a/packages/component/src/define-component.js +++ b/packages/component/src/define-component.js @@ -113,8 +113,32 @@ export const defineComponent = ({ super.connectedCallback(); } + triggerAttributeChange() { + each(webComponent.properties, (propSettings, property) => { + const attribute = camelToKebab(property); + + let newValue = this[property]; + // this is necessary to handle how lit handles boolean attributes + // otherwise you get + if(!propSettings.alias && newValue === true) { + this.setAttribute(attribute, ''); + } + adjustPropertyFromAttribute({ + el: this, + attribute, + properties: webComponent.properties, + attributeValue: newValue, + componentSpec + }); + }); + } + willUpdate() { - super.willUpdate(); + if(isServer) { + // property change callbacks wont call on SSR + // we need this to get proper settings for server render + this.triggerAttributeChange(); + } if(!this.template) { this.template = litTemplate.clone({ data: this.getData(), @@ -128,25 +152,7 @@ export const defineComponent = ({ this.component = this.template.instance; this.dataContext = this.template.data; } - // property change callbacks wont call on SSR - if(isServer) { - each(webComponent.properties, (propSettings, property) => { - let newValue = this[property]; - const attribute = camelToKebab(property); - - // this is necessary to handle how lit handles boolean attributes - // otherwise you get - if(newValue === true) { - this.setAttribute(attribute, ''); - } - adjustPropertyFromAttribute({ - el: this, - attribute, - attributeValue: newValue, - componentSpec - }); - }); - } + super.willUpdate(); } firstUpdated() { @@ -178,6 +184,7 @@ export const defineComponent = ({ el: this, attribute, attributeValue: newValue, + properties: webComponent.properties, componentSpec }); this.call(onAttributeChanged, { args: [attribute, oldValue, newValue], }); diff --git a/packages/component/src/helpers/adjust-property-from-attribute.js b/packages/component/src/helpers/adjust-property-from-attribute.js index af8e3c8ca..d6dd40245 100644 --- a/packages/component/src/helpers/adjust-property-from-attribute.js +++ b/packages/component/src/helpers/adjust-property-from-attribute.js @@ -1,4 +1,4 @@ -import { get, each, unique, firstMatch, inArray, isString, kebabToCamel, camelToKebab } from '@semantic-ui/utils'; +import { get, each, unique, firstMatch, inArray, isString, kebabToCamel } from '@semantic-ui/utils'; /* Semantic UI supports 3 dialects to support this we @@ -40,13 +40,14 @@ const tokenizeSpaces = (string) => { - The option is the attribute "primary" or "primary=true" - The option is a class class="primary" */ -export const adjustPropertyFromAttribute = ({el, attribute, attributeValue, componentSpec}) => { +export const adjustPropertyFromAttribute = ({el, attribute, attributeValue, properties, componentSpec}) => { // This is used to search for potential values that should match to the canonical value. // This is because we support swapping ordering and spaces for dashes // note "optionAttributeValue" is the value of the option attribute i.e. "somevalue" here // i.e + const checkSpecForAllowedValue = ({attribute, optionValue, optionAttributeValue }) => { // "arrow down" -> arrow-down @@ -193,5 +194,25 @@ export const adjustPropertyFromAttribute = ({el, attribute, attributeValue, comp } } } + else if(properties && attributeValue !== undefined && attribute.includes('-')) { + + /* This handles the case of multiword properties like `useAccordion` + maps to or + the kebab case is just an alias which will update the base setting + */ + const propertyName = kebabToCamel(attribute); + const attributeSettings = properties[attribute]; + if(propertyName !== attribute && attributeSettings?.alias) { + const convertFunc = attributeSettings?.converter?.fromAttribute; + let propertyValue = (convertFunc) + ? convertFunc(attributeValue) + : attributeValue + ; + if(propertyValue) { + setProperty(propertyName, propertyValue); + } + return; + } + } }; diff --git a/packages/component/src/web-component.js b/packages/component/src/web-component.js index 6aeb2bf9e..c7a700aea 100755 --- a/packages/component/src/web-component.js +++ b/packages/component/src/web-component.js @@ -67,7 +67,7 @@ class WebComponentBase extends LitElement { return properties; } if (componentSpec) { - properties.class = { type: String }; + properties.class = { type: String, noAccessor: true, alias: true }; // emphasis="primary" but also setting props each(componentSpec.attributes, (attributeName) => { @@ -77,17 +77,18 @@ class WebComponentBase extends LitElement { }); // these are values that can only be set on the DOM el as properties - // but do not have attributes -- for instance functions + // but do not have attributes like functions or classes each(componentSpec.properties, (attributeName) => { const propertyType = componentSpec.propertyTypes[attributeName]; const propertyName = kebabToCamel(attributeName); properties[propertyName] = WebComponentBase.getPropertySettings(attributeName, propertyType); }); - // primary -> emphasis="primary" + // this handles syntax where allowed value is used as attribute + // -> emphasis="primary" each(componentSpec.optionAttributes, (attributeValues, attributeName) => { const propertyName = kebabToCamel(attributeName); - properties[propertyName] = { type: String, noAccessor: true, attribute: attributeName }; + properties[propertyName] = { type: String, noAccessor: true, alias: true, attribute: attributeName }; }); } if (settings) { @@ -100,25 +101,35 @@ class WebComponentBase extends LitElement { const propertySettings = { propertyOnly: isClassInstance(defaultValue) }; - const attributeName = camelToKebab(propertyName); + properties[propertyName] = (defaultValue?.type) ? settings - : WebComponentBase.getPropertySettings(attributeName, defaultValue?.constructor, propertySettings) + : WebComponentBase.getPropertySettings(propertyName, defaultValue?.constructor, propertySettings) ; }); } + + /* This handles the case of multiword settings like `useAccordion` + we support 2 syntax or ' + the kebab attr serves as an alias with no accessor + */ + each(properties, (propertySettings, propertyName) => { + const attributeName = camelToKebab(propertyName); + if(attributeName !== propertyName && !properties[attributeName] && properties[propertyName]) { + properties[attributeName] = { + ...properties[propertyName], + noAccessor: true, + alias: true, + }; + } + }); return properties; } - static getPropertySettings(propName, type = String, { propertyOnly = false } = {}) { + static getPropertySettings(propertyName, type = String, { propertyOnly = false } = {}) { let property = { type, - /* - Lit converts properties to lowercase name instead of kebab case - i.e. firstName -> - we want `first-name="John"`, this means we need to manually specify attribute - */ - attribute: camelToKebab(propName), + attribute: true, hasChanged: (a, b) => { return !isEqual(a, b); }, @@ -184,10 +195,9 @@ class WebComponentBase extends LitElement { getSettingsFromConfig({componentSpec, properties}) { let settings = {}; each(properties, (propSettings, propertyName) => { - if (propertyName == 'class' || propSettings.observe === false) { + if (propSettings.alias === true) { return; } - const attributeName = camelToKebab(propertyName); const elementProp = this[propertyName]; const setting = elementProp // check element setting ?? this.defaultSettings[propertyName] // check default setting on this component @@ -195,13 +205,6 @@ class WebComponentBase extends LitElement { ; // only pass through setting if it is defined if(setting !== undefined) { - - // if setting is a composite like emphasis="primary" - // and this is "primary" we pass this as a boolean - if(componentSpec && !get(componentSpec.allowedValues, attributeName) && get(componentSpec.optionAttributes, attributeName)) { - settings[propertyName] = true; - return; - } settings[propertyName] = setting; } // boolean attribute case