@descope-ui/common 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.eslintrc.json +18 -0
  2. package/README.md +7 -0
  3. package/package.json +36 -0
  4. package/project.json +7 -0
  5. package/src/baseClasses/baseClasses/createBaseClass.js +66 -0
  6. package/src/baseClasses/baseClasses/createBaseInputClass.js +14 -0
  7. package/src/baseClasses/baseClasses/createCssVarImageClass.js +55 -0
  8. package/src/baseClasses/index.js +3 -0
  9. package/src/componentsHelpers/index.js +95 -0
  10. package/src/componentsMixins/helpers/mixinsHelpers.js +10 -0
  11. package/src/componentsMixins/index.js +1 -0
  12. package/src/componentsMixins/mixins/activableMixin.js +14 -0
  13. package/src/componentsMixins/mixins/changeMixin.js +22 -0
  14. package/src/componentsMixins/mixins/componentNameValidationMixin.js +23 -0
  15. package/src/componentsMixins/mixins/componentsContextMixin.js +12 -0
  16. package/src/componentsMixins/mixins/createDynamicDataMixin.js +100 -0
  17. package/src/componentsMixins/mixins/createProxy.js +58 -0
  18. package/src/componentsMixins/mixins/createStyleMixin/helpers.js +106 -0
  19. package/src/componentsMixins/mixins/createStyleMixin/index.js +167 -0
  20. package/src/componentsMixins/mixins/draggableMixin.js +62 -0
  21. package/src/componentsMixins/mixins/externalInputHelpers.js +93 -0
  22. package/src/componentsMixins/mixins/externalInputMixin.js +170 -0
  23. package/src/componentsMixins/mixins/hoverableMixin.js +13 -0
  24. package/src/componentsMixins/mixins/index.js +14 -0
  25. package/src/componentsMixins/mixins/inputEventsDispatchingMixin.js +76 -0
  26. package/src/componentsMixins/mixins/inputValidationMixin.js +210 -0
  27. package/src/componentsMixins/mixins/lifecycleEventsMixin.js +23 -0
  28. package/src/componentsMixins/mixins/normalizeBooleanAttributesMixin.js +59 -0
  29. package/src/componentsMixins/mixins/portalMixin.js +112 -0
  30. package/src/componentsMixins/mixins/proxyInputMixin.js +242 -0
  31. package/src/constants.js +4 -0
  32. package/src/icons/errorMessageIconBase64.js +1 -0
  33. package/src/sbControls.js +302 -0
  34. package/src/sbHelpers.js +53 -0
  35. package/src/themeHelpers/colorsHelpers.js +94 -0
  36. package/src/themeHelpers/componentsThemeManager.js +45 -0
  37. package/src/themeHelpers/index.js +191 -0
  38. package/src/themeHelpers/resetHelpers.js +144 -0
  39. package/src/utils/index.js +68 -0
package/.eslintrc.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "extends": ["../../../.eslintrc.json"],
3
+ "ignorePatterns": ["!**/*"],
4
+ "overrides": [
5
+ {
6
+ "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7
+ "rules": {}
8
+ },
9
+ {
10
+ "files": ["*.ts", "*.tsx"],
11
+ "rules": {}
12
+ },
13
+ {
14
+ "files": ["*.js", "*.jsx"],
15
+ "rules": {}
16
+ }
17
+ ]
18
+ }
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # common
2
+
3
+ This library was generated with [Nx](https://nx.dev).
4
+
5
+ ## Running unit tests
6
+
7
+ Run `nx test common` to execute the unit tests via [Jest](https://jestjs.io).
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@descope-ui/common",
3
+ "version": "0.0.1",
4
+ "dependencies": {
5
+ "element-internals-polyfill": "^1.3.9",
6
+ "color": "^4.2.3",
7
+ "lodash.merge": "4.6.2"
8
+ },
9
+ "exports": {
10
+ "./constants": {
11
+ "import": "./src/constants.js"
12
+ },
13
+ "./utils": {
14
+ "import": "./src/utils/index.js"
15
+ },
16
+ "./theme-helpers": {
17
+ "import": "./src/themeHelpers/index.js",
18
+ "require": "./src/themeHelpers/index.js"
19
+ },
20
+ "./components-mixins": {
21
+ "import": "./src/componentsMixins/index.js"
22
+ },
23
+ "./base-classes": {
24
+ "import": "./src/baseClasses/index.js"
25
+ },
26
+ "./components-helpers": {
27
+ "import": "./src/componentsHelpers/index.js"
28
+ },
29
+ "./sb-controls": {
30
+ "import": "./src/sbControls.js"
31
+ },
32
+ "./sb-helpers": {
33
+ "import": "./src/sbHelpers.js"
34
+ }
35
+ }
36
+ }
package/project.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "@descope-ui/common",
3
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "packages/libs/common/src",
5
+ "projectType": "library",
6
+ "tags": []
7
+ }
@@ -0,0 +1,66 @@
1
+ import { compose } from '../../utils';
2
+ import { componentNameValidationMixin } from '../../componentsMixins/mixins/componentNameValidationMixin';
3
+ import { componentsContextMixin } from '../../componentsMixins/mixins/componentsContextMixin';
4
+ import { hoverableMixin } from '../../componentsMixins/mixins/hoverableMixin';
5
+ import { normalizeBooleanAttributesMixin } from '../../componentsMixins/mixins/normalizeBooleanAttributesMixin';
6
+
7
+ export const createBaseClass = ({ componentName, baseSelector = '' }) => {
8
+ class DescopeBaseClass extends HTMLElement {
9
+ static get componentName() {
10
+ return componentName;
11
+ }
12
+
13
+ #baseElement;
14
+
15
+ #isInit = true;
16
+
17
+ // base selector is the selector for the component wrapper,
18
+ // it's the highest element that is relevant for the layout
19
+ // eslint-disable-next-line class-methods-use-this
20
+ get baseSelector() {
21
+ return baseSelector;
22
+ }
23
+
24
+ // this is the base element, which returned by querying the base selector
25
+ get baseElement() {
26
+ this.#baseElement ??= this.baseSelector
27
+ ? this.rootElement.querySelector(this.baseSelector)
28
+ : this;
29
+
30
+ if (!this.#baseElement) {
31
+ // eslint-disable-next-line no-console
32
+ console.warn('missing base element for component', this.localName);
33
+ }
34
+ return this.#baseElement;
35
+ }
36
+
37
+ // this is the component root level element,
38
+ // it can be either a shadow root or the component's node from the light DOM
39
+ get rootElement() {
40
+ return this.shadowRoot || this;
41
+ }
42
+
43
+ get name() {
44
+ return this.getAttribute('name');
45
+ }
46
+
47
+ connectedCallback() {
48
+ super.connectedCallback?.();
49
+
50
+ if (this.rootElement.isConnected) {
51
+ // the init function is running once, on the first time the component is connected
52
+ if (this.#isInit) {
53
+ this.#isInit = false;
54
+ this.init?.();
55
+ }
56
+ }
57
+ }
58
+ }
59
+
60
+ return compose(
61
+ componentNameValidationMixin,
62
+ hoverableMixin,
63
+ normalizeBooleanAttributesMixin,
64
+ componentsContextMixin
65
+ )(DescopeBaseClass);
66
+ };
@@ -0,0 +1,14 @@
1
+ import { compose } from '../../utils';
2
+ import { changeMixin } from '../../componentsMixins/mixins/changeMixin';
3
+ import { inputEventsDispatchingMixin } from '../../componentsMixins/mixins/inputEventsDispatchingMixin';
4
+ import { inputValidationMixin } from '../../componentsMixins/mixins/inputValidationMixin';
5
+ import { normalizeBooleanAttributesMixin } from '../../componentsMixins/mixins/normalizeBooleanAttributesMixin';
6
+ import { createBaseClass } from './createBaseClass';
7
+
8
+ export const createBaseInputClass = (...args) =>
9
+ compose(
10
+ inputValidationMixin,
11
+ changeMixin,
12
+ normalizeBooleanAttributesMixin,
13
+ inputEventsDispatchingMixin
14
+ )(createBaseClass(...args));
@@ -0,0 +1,55 @@
1
+ import { compose } from '../../utils';
2
+ import { componentNameValidationMixin } from '../../componentsMixins/mixins/componentNameValidationMixin';
3
+ import { createStyleMixin } from '../../componentsMixins/mixins/createStyleMixin';
4
+ import { draggableMixin } from '../../componentsMixins/mixins/draggableMixin';
5
+ import { createBaseClass } from './createBaseClass';
6
+
7
+ export const createCssVarImageClass = ({ componentName, varName, fallbackVarName }) => {
8
+ let style;
9
+ const getContent = () => style;
10
+
11
+ class RawCssVarImageClass extends createBaseClass({
12
+ componentName,
13
+ baseSelector: ':host > div',
14
+ }) {
15
+ constructor() {
16
+ super();
17
+ this.attachShadow({ mode: 'open' }).innerHTML = `
18
+ <style>
19
+ :host {
20
+ display: inline-flex;
21
+ }
22
+ :host([draggable="true"]) > div {
23
+ pointer-events: none
24
+ }
25
+ :host > div {
26
+ display: inline-block;
27
+ max-width: 100%;
28
+ max-height: 100%;
29
+ object-fit: contain;
30
+ margin: auto;
31
+ ${getContent()}
32
+ }
33
+ </style>
34
+ <div></div>
35
+ `;
36
+ }
37
+ }
38
+
39
+ const CssVarImageClass = compose(
40
+ createStyleMixin({
41
+ mappings: {
42
+ height: { selector: () => ':host > div' },
43
+ width: { selector: () => ':host > div' },
44
+ [varName]: { property: 'content' },
45
+ [fallbackVarName]: { property: 'content' },
46
+ },
47
+ }),
48
+ draggableMixin,
49
+ componentNameValidationMixin
50
+ )(RawCssVarImageClass);
51
+
52
+ style = `content: var(${CssVarImageClass.cssVarList[varName]}, var(${CssVarImageClass.cssVarList[fallbackVarName]}));`;
53
+
54
+ return CssVarImageClass;
55
+ };
@@ -0,0 +1,3 @@
1
+ export * from './baseClasses/createBaseClass';
2
+ export * from './baseClasses/createCssVarImageClass';
3
+ export * from './baseClasses/createBaseInputClass';
@@ -0,0 +1,95 @@
1
+ import { kebabCaseJoin } from '../utils';
2
+ import { DESCOPE_PREFIX } from '../constants';
3
+
4
+ export const observeAttributes = (ele, callback, { excludeAttrs = [], includeAttrs = [] }) => {
5
+ // sync all attrs on init
6
+ const filteredAttrs = Array.from(ele.attributes)
7
+ .filter(
8
+ (attr) =>
9
+ !excludeAttrs.includes(attr.name) &&
10
+ (!includeAttrs.length || includeAttrs.includes(attr.name))
11
+ )
12
+ .map((attr) => attr.name);
13
+
14
+ callback(filteredAttrs);
15
+
16
+ const observer = new MutationObserver((mutationsList) => {
17
+ mutationsList.forEach((mutation) => {
18
+ if (
19
+ mutation.type === 'attributes' &&
20
+ !excludeAttrs.includes(mutation.attributeName) &&
21
+ (!includeAttrs.length || includeAttrs.includes(mutation.attributeName))
22
+ ) {
23
+ callback([mutation.attributeName]);
24
+ }
25
+ });
26
+ });
27
+
28
+ observer.observe(ele, { attributes: true });
29
+ };
30
+
31
+ // calling the callback with this object: { addedNodes, removedNodes }
32
+ export const observeChildren = (ele, callback) => {
33
+ callback({ addedNodes: Array.from(ele.children), removedNodes: [] });
34
+
35
+ const observer = new MutationObserver((mutationsList) => {
36
+ mutationsList.forEach((mutation) => {
37
+ if (mutation.type === 'childList' || mutation.type === 'characterData') {
38
+ callback(mutation);
39
+ }
40
+ });
41
+ });
42
+
43
+ observer.observe(ele, { childList: true, characterData: true, subtree: true });
44
+ };
45
+
46
+ const createSyncAttrsCb =
47
+ (srcEle, targetEle, mapAttrs = {}) =>
48
+ (attrNames) => {
49
+ attrNames.forEach((attrName) => {
50
+ const targetAttrName = mapAttrs[attrName] || attrName;
51
+ const srcAttrVal = srcEle.getAttribute(attrName);
52
+ if (srcAttrVal !== null) {
53
+ if (targetEle.getAttribute(targetAttrName) !== srcAttrVal) {
54
+ targetEle.setAttribute(targetAttrName, srcAttrVal);
55
+ }
56
+ } else {
57
+ targetEle.removeAttribute(targetAttrName);
58
+ }
59
+ });
60
+ };
61
+
62
+ export const syncAttrs = (ele1, ele2, options) => {
63
+ observeAttributes(ele1, createSyncAttrsCb(ele1, ele2), options);
64
+ observeAttributes(ele2, createSyncAttrsCb(ele2, ele1), options);
65
+ };
66
+
67
+ export const getComponentName = (name) => kebabCaseJoin(DESCOPE_PREFIX, name);
68
+
69
+ export const getCssVarName = (...args) => `--${kebabCaseJoin(...args)}`;
70
+
71
+ export const forwardAttrs = (source, dest, options = {}) => {
72
+ observeAttributes(source, createSyncAttrsCb(source, dest, options.mapAttrs), options);
73
+ };
74
+
75
+ export const forwardProps = (src, target, props = []) => {
76
+ if (!props.length) return;
77
+
78
+ const config = props.reduce(
79
+ (acc, prop) =>
80
+ Object.assign(acc, {
81
+ [prop]: {
82
+ get() {
83
+ return src[prop];
84
+ },
85
+ set(v) {
86
+ // eslint-disable-next-line no-param-reassign
87
+ src[prop] = v;
88
+ },
89
+ },
90
+ }),
91
+ {}
92
+ );
93
+
94
+ Object.defineProperties(target, config);
95
+ };
@@ -0,0 +1,10 @@
1
+ // create a dispatch event function that also calls to the onevent function in case it's set
2
+ // usage example:
3
+ // #dispatchSomething = createDispatchEvent.bind(this, 'something' { ...options })
4
+ // this will dispatch a new event when called, but also call to "onsomething"
5
+ export function createDispatchEvent(eventName, options = {}) {
6
+ const event = new Event(eventName, options);
7
+
8
+ this[`on${eventName}`]?.(event); // in case we got an event callback as property
9
+ this.dispatchEvent(event);
10
+ }
@@ -0,0 +1 @@
1
+ export * from './mixins'
@@ -0,0 +1,14 @@
1
+ export const activeableMixin = (superclass) =>
2
+ class ActiveableMixinClass extends superclass {
3
+ init() {
4
+ super.init?.();
5
+
6
+ this.baseElement.addEventListener('mousedown', (e) => {
7
+ e.preventDefault();
8
+ this.setAttribute('active', 'true');
9
+ window.addEventListener('mouseup', () => this.removeAttribute('active'), {
10
+ once: true,
11
+ });
12
+ });
13
+ }
14
+ };
@@ -0,0 +1,22 @@
1
+ import { createDispatchEvent } from '../../componentsMixins/helpers/mixinsHelpers';
2
+
3
+ export const changeMixin = (superclass) =>
4
+ class ChangeMixinClass extends superclass {
5
+ #dispatchChange = createDispatchEvent.bind(this, 'change');
6
+
7
+ init() {
8
+ super.init?.();
9
+ this.prevValue = this.value;
10
+
11
+ this.addEventListener('change', (e) => {
12
+ e.stopPropagation();
13
+ });
14
+
15
+ this.addEventListener('blur', () => {
16
+ if (this.value !== this.prevValue) {
17
+ this.#dispatchChange();
18
+ this.prevValue = this.value;
19
+ }
20
+ });
21
+ }
22
+ };
@@ -0,0 +1,23 @@
1
+ export const componentNameValidationMixin = (superclass) =>
2
+ class ComponentNameValidationMixinClass extends superclass {
3
+ #checkComponentName() {
4
+ const currentComponentName = this.localName;
5
+
6
+ if (!superclass.componentName) {
7
+ throw Error(
8
+ `component name is not defined on super class, make sure you have a static get for "componentName"`
9
+ );
10
+ }
11
+
12
+ if (currentComponentName !== superclass.componentName) {
13
+ throw Error(
14
+ `component name mismatch, expected "${superclass.componentName}", current "${currentComponentName}"`
15
+ );
16
+ }
17
+ }
18
+
19
+ init() {
20
+ super.init?.();
21
+ this.#checkComponentName();
22
+ }
23
+ };
@@ -0,0 +1,12 @@
1
+ export const componentsContextMixin = (superclass) =>
2
+ class ComponentsContextMixinClass extends superclass {
3
+ updateComponentsContext(componentsContext) {
4
+ this.dispatchEvent(
5
+ new CustomEvent('components-context', {
6
+ bubbles: true,
7
+ composed: true,
8
+ detail: componentsContext,
9
+ })
10
+ );
11
+ }
12
+ };
@@ -0,0 +1,100 @@
1
+ import { observeAttributes } from '../helpers/componentHelpers';
2
+
3
+ const defaultValidateSchema = () => true;
4
+ const defaultItemRenderer = (item) => `<pre>${JSON.stringify(item, null, 4)}</pre>`;
5
+
6
+ const createTemplate = (templateString) => {
7
+ const template = document.createElement('template');
8
+ template.innerHTML = templateString;
9
+
10
+ return template;
11
+ };
12
+
13
+ const getTemplateContent = (templateOrString) => {
14
+ if (typeof templateOrString === 'string') {
15
+ return createTemplate(templateOrString).content;
16
+ }
17
+
18
+ if (templateOrString instanceof HTMLTemplateElement) {
19
+ return templateOrString.content;
20
+ }
21
+
22
+ // eslint-disable-next-line no-console
23
+ console.error('Invalid template', templateOrString);
24
+ return null;
25
+ };
26
+
27
+ export const createDynamicDataMixin =
28
+ ({
29
+ itemRenderer = defaultItemRenderer,
30
+ validateSchema = defaultValidateSchema,
31
+ slotName,
32
+ rerenderAttrsList = [],
33
+ }) =>
34
+ (superclass) =>
35
+ class DynamicDataMixinClass extends superclass {
36
+ #data = [];
37
+
38
+ // eslint-disable-next-line class-methods-use-this
39
+ #validateSchema(data) {
40
+ if (!validateSchema) return true;
41
+
42
+ const validation = validateSchema(data);
43
+ if (validation === true) return true;
44
+
45
+ // eslint-disable-next-line no-console
46
+ console.error('Data schema validation failed', validation || '');
47
+
48
+ return false;
49
+ }
50
+
51
+ #removeOldItems() {
52
+ const selector = slotName ? `*[slot="${slotName}"]` : ':not([slot])';
53
+ this.baseElement.querySelectorAll(selector).forEach((item) => item.remove());
54
+ }
55
+
56
+ #renderItems() {
57
+ this.#removeOldItems();
58
+ this.data.forEach((item, index) => {
59
+ const content = getTemplateContent(itemRenderer(item, index, this));
60
+ this.baseElement.appendChild(content?.cloneNode(true));
61
+ });
62
+ }
63
+
64
+ set data(value) {
65
+ if (this.#validateSchema(value)) {
66
+ this.#data = value;
67
+ this.#renderItems();
68
+ }
69
+ }
70
+
71
+ get data() {
72
+ return this.#data;
73
+ }
74
+
75
+ init() {
76
+ super.init?.();
77
+
78
+ observeAttributes(
79
+ this,
80
+ (attrs) => {
81
+ if (attrs.includes('data')) this.#handleDataAttr();
82
+ else this.#renderItems();
83
+ },
84
+ { includeAttrs: [...rerenderAttrsList, 'data'] }
85
+ );
86
+ }
87
+
88
+ #handleDataAttr() {
89
+ const dataAttr = this.getAttribute('data');
90
+
91
+ if (!dataAttr) return;
92
+
93
+ try {
94
+ this.data = JSON.parse(dataAttr);
95
+ } catch (e) {
96
+ // eslint-disable-next-line no-console
97
+ console.warn('Invalid JSON data', dataAttr);
98
+ }
99
+ }
100
+ };
@@ -0,0 +1,58 @@
1
+ import { createBaseClass } from '../../baseClasses';
2
+ import { isFunction } from '../../utils';
3
+ import { forwardProps, syncAttrs } from '../../componentsHelpers';
4
+ import { createDispatchEvent } from '../../componentsMixins/helpers/mixinsHelpers';
5
+
6
+ export const createProxy = ({
7
+ componentName,
8
+ wrappedEleName,
9
+ slots = [],
10
+ style,
11
+ excludeAttrsSync = [],
12
+ includeAttrsSync = [],
13
+ includeForwardProps = [],
14
+ delegatesFocus = true,
15
+ }) => {
16
+ class ProxyClass extends createBaseClass({ componentName, baseSelector: wrappedEleName }) {
17
+ #dispatchBlur = createDispatchEvent.bind(this, 'blur');
18
+
19
+ #dispatchFocus = createDispatchEvent.bind(this, 'focus');
20
+
21
+ constructor() {
22
+ super().attachShadow({ mode: 'open', delegatesFocus }).innerHTML = `
23
+ <style id="create-proxy">${(isFunction(style) ? style() : style) || ''}</style>
24
+ <${wrappedEleName}>
25
+ ${slots
26
+ .map(
27
+ (
28
+ slot // when slot is an empty string, we will create the default slot (without a name)
29
+ ) => `<slot ${slot ? `name="${slot}" slot="${slot}"` : ''}></slot>`
30
+ )
31
+ .join('')}
32
+ </${wrappedEleName}>
33
+ `;
34
+ }
35
+
36
+ init() {
37
+ super.init?.();
38
+
39
+ this.baseElement.addEventListener('blur', (_) => {
40
+ this.#dispatchBlur();
41
+ });
42
+
43
+ this.baseElement.addEventListener('focus', (_) => {
44
+ this.#dispatchFocus();
45
+ });
46
+
47
+ // this is needed for components that uses props, such as combo box
48
+ forwardProps(this.baseElement, this, includeForwardProps);
49
+
50
+ syncAttrs(this.baseElement, this, {
51
+ excludeAttrs: excludeAttrsSync,
52
+ includeAttrs: includeAttrsSync,
53
+ });
54
+ }
55
+ }
56
+
57
+ return ProxyClass;
58
+ };
@@ -0,0 +1,106 @@
1
+ import { camelCaseJoin, isFunction, kebabCase, kebabCaseJoin } from '../../../utils';
2
+ import { getCssVarName } from '../../../componentsHelpers';
3
+
4
+ const createCssVar = (varName, fallback) => `var(${varName}${fallback ? `, ${fallback}` : ''})`;
5
+
6
+ const createCssSelector = (baseSelector = '', relativeSelectorOrSelectorFn = '') =>
7
+ isFunction(relativeSelectorOrSelectorFn)
8
+ ? relativeSelectorOrSelectorFn(baseSelector)
9
+ : `${baseSelector}${
10
+ /^[A-Za-z]/.test(relativeSelectorOrSelectorFn)
11
+ ? ` ${relativeSelectorOrSelectorFn}`
12
+ : relativeSelectorOrSelectorFn
13
+ }`;
14
+
15
+ class StyleBuilder {
16
+ constructor() {
17
+ this.styleMap = new Map();
18
+ }
19
+
20
+ add(selector, property, value) {
21
+ if (!this.styleMap.has(selector)) {
22
+ this.styleMap.set(selector, []);
23
+ }
24
+
25
+ this.styleMap.set(selector, [...this.styleMap.get(selector), { property, value }]);
26
+ }
27
+
28
+ toString() {
29
+ return Array.from(this.styleMap.entries()).reduce((acc, [selector, propValArr]) => {
30
+ const properties = propValArr
31
+ .map(({ property, value }) => `${property}: ${value}`)
32
+ .join(';\n');
33
+
34
+ return `${acc}${selector} { \n${properties} \n}\n\n`;
35
+ }, '');
36
+ }
37
+ }
38
+
39
+ const normalizeConfig = (attr, config) => {
40
+ const defaultMapping = { selector: '', property: kebabCase(attr) };
41
+
42
+ if (!config || !Object.keys(config).length) return [defaultMapping];
43
+
44
+ if (Array.isArray(config)) return config.map((entry) => ({ ...defaultMapping, ...entry }));
45
+
46
+ return [{ ...defaultMapping, ...config }];
47
+ };
48
+
49
+ const getFallbackVarName = (origVarName, suffix = 'fallback') => kebabCaseJoin(origVarName, suffix);
50
+
51
+ export const createStyle = (componentName, baseSelector, mappings) => {
52
+ const style = new StyleBuilder();
53
+ const createFallbackVar = (fallback, origVarName) => {
54
+ if (!fallback) return '';
55
+ if (typeof fallback === 'string') return fallback;
56
+
57
+ const fallbackVarName = getFallbackVarName(origVarName, fallback?.suffix);
58
+ return createCssVar(fallbackVarName, createFallbackVar(fallback.fallback, fallbackVarName));
59
+ };
60
+
61
+ Object.keys(mappings).forEach((attr) => {
62
+ const attrConfig = normalizeConfig(attr, mappings[attr]);
63
+
64
+ const cssVarName = getCssVarName(componentName, attr);
65
+
66
+ attrConfig.forEach(
67
+ ({ selector: relativeSelectorOrSelectorFn, property, important, fallback }) => {
68
+ const fallbackValue = createFallbackVar(fallback, cssVarName);
69
+ style.add(
70
+ createCssSelector(baseSelector, relativeSelectorOrSelectorFn),
71
+ isFunction(property) ? property() : property,
72
+ createCssVar(cssVarName, fallbackValue) + (important ? '!important' : '')
73
+ );
74
+ }
75
+ );
76
+ });
77
+
78
+ return style.toString();
79
+ };
80
+
81
+ const getFallbackVarsNames = (attr, origVarName, { fallback }) => {
82
+ if (!fallback) return {};
83
+
84
+ const fallbackVarName = getFallbackVarName(origVarName, fallback.suffix);
85
+ const fallbackAttrName = camelCaseJoin(attr, fallback.suffix || 'fallback');
86
+ return {
87
+ [fallbackAttrName]: fallbackVarName,
88
+ ...getFallbackVarsNames(fallbackAttrName, fallbackVarName, fallback),
89
+ };
90
+ };
91
+
92
+ export const createCssVarsList = (componentName, mappings) =>
93
+ Object.keys(mappings).reduce((acc, attr) => {
94
+ const varName = getCssVarName(componentName, attr);
95
+
96
+ return Object.assign(
97
+ acc,
98
+ { [attr]: varName },
99
+ getFallbackVarsNames(attr, varName, mappings[attr])
100
+ );
101
+ }, {});
102
+
103
+ // on some cases we need a selector to be more specific than another
104
+ // for this we have this fn that generate a class selector multiple times
105
+ export const createClassSelectorSpecifier = (className, numOfRepeats) =>
106
+ Array(numOfRepeats).fill(`.${className}`).join('');