@descope-ui/common 0.0.18 → 0.1.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [0.1.1](https://github.com/descope/web-components-ui/compare/@descope-ui/common-0.1.0...@descope-ui/common-0.1.1) (2025-09-29)
6
+
7
+ ## [0.1.0](https://github.com/descope/web-components-ui/compare/@descope-ui/common-0.0.18...@descope-ui/common-0.1.0) (2025-09-02)
8
+
9
+
10
+ ### Features
11
+
12
+ * Trusted Devices ([#697](https://github.com/descope/web-components-ui/issues/697)) ([fb2f0eb](https://github.com/descope/web-components-ui/commit/fb2f0eb6773ed624354ed9f0b97d713bb8b10fce))
13
+
5
14
  ## [0.0.18](https://github.com/descope/web-components-ui/compare/@descope-ui/common-0.0.17...@descope-ui/common-0.0.18) (2025-07-21)
6
15
 
7
16
  ## [0.0.17](https://github.com/descope/web-components-ui/compare/@descope-ui/common-0.0.16...@descope-ui/common-0.0.17) (2025-07-15)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@descope-ui/common",
3
- "version": "0.0.18",
3
+ "version": "0.1.1",
4
4
  "dependencies": {
5
5
  "element-internals-polyfill": "^1.3.9",
6
6
  "color": "^4.2.3",
@@ -1,13 +1,17 @@
1
1
  import { kebabCaseJoin } from '../utils';
2
2
  import { DESCOPE_PREFIX } from '../constants';
3
3
 
4
- export const observeAttributes = (ele, callback, { excludeAttrs = [], includeAttrs = [] }) => {
4
+ export const observeAttributes = (
5
+ ele,
6
+ callback,
7
+ { excludeAttrs = [], includeAttrs = [] },
8
+ ) => {
5
9
  // sync all attrs on init
6
10
  const filteredAttrs = Array.from(ele.attributes)
7
11
  .filter(
8
12
  (attr) =>
9
13
  !excludeAttrs.includes(attr.name) &&
10
- (!includeAttrs.length || includeAttrs.includes(attr.name))
14
+ (!includeAttrs.length || includeAttrs.includes(attr.name)),
11
15
  )
12
16
  .map((attr) => attr.name);
13
17
 
@@ -40,24 +44,28 @@ export const observeChildren = (ele, callback) => {
40
44
  });
41
45
  });
42
46
 
43
- observer.observe(ele, { childList: true, characterData: true, subtree: true });
47
+ observer.observe(ele, {
48
+ childList: true,
49
+ characterData: true,
50
+ subtree: true,
51
+ });
44
52
  };
45
53
 
46
54
  const createSyncAttrsCb =
47
55
  (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);
56
+ (attrNames) => {
57
+ attrNames.forEach((attrName) => {
58
+ const targetAttrName = mapAttrs[attrName] || attrName;
59
+ const srcAttrVal = srcEle.getAttribute(attrName);
60
+ if (srcAttrVal !== null) {
61
+ if (targetEle.getAttribute(targetAttrName) !== srcAttrVal) {
62
+ targetEle.setAttribute(targetAttrName, srcAttrVal);
58
63
  }
59
- });
60
- };
64
+ } else {
65
+ targetEle.removeAttribute(targetAttrName);
66
+ }
67
+ });
68
+ };
61
69
 
62
70
  export const syncAttrs = (ele1, ele2, options) => {
63
71
  observeAttributes(ele1, createSyncAttrsCb(ele1, ele2), options);
@@ -69,7 +77,11 @@ export const getComponentName = (name) => kebabCaseJoin(DESCOPE_PREFIX, name);
69
77
  export const getCssVarName = (...args) => `--${kebabCaseJoin(...args)}`;
70
78
 
71
79
  export const forwardAttrs = (source, dest, options = {}) => {
72
- observeAttributes(source, createSyncAttrsCb(source, dest, options.mapAttrs), options);
80
+ observeAttributes(
81
+ source,
82
+ createSyncAttrsCb(source, dest, options.mapAttrs),
83
+ options,
84
+ );
73
85
  };
74
86
 
75
87
  export const forwardProps = (src, target, props = []) => {
@@ -88,14 +100,13 @@ export const forwardProps = (src, target, props = []) => {
88
100
  },
89
101
  },
90
102
  }),
91
- {}
103
+ {},
92
104
  );
93
105
 
94
106
  Object.defineProperties(target, config);
95
107
  };
96
108
 
97
109
  export const injectStyle = (cssString, ref, { prepend = false } = {}) => {
98
-
99
110
  let style;
100
111
  try {
101
112
  style = new CSSStyleSheet();
@@ -109,14 +120,14 @@ export const injectStyle = (cssString, ref, { prepend = false } = {}) => {
109
120
  style.replaceSync(cssString);
110
121
  }
111
122
  if (_ref) {
112
- const adoptedStyleSheets = [...(_ref.adoptedStyleSheets || [])]
123
+ const adoptedStyleSheets = [...(_ref.adoptedStyleSheets || [])];
113
124
  adoptedStyleSheets[prepend ? 'unshift' : 'push'](style);
114
125
 
115
126
  _ref.adoptedStyleSheets = adoptedStyleSheets;
116
127
  }
117
128
 
118
129
  return style;
119
- }
130
+ };
120
131
 
121
132
  // we should mimic the CSSStyleSheet API for the fns we are using
122
133
  class CSSStyleSheetMock {
@@ -127,7 +138,7 @@ class CSSStyleSheetMock {
127
138
  this.ref = ref?.shadowRoot || ref;
128
139
 
129
140
  if (!this.ref) {
130
- return
141
+ return;
131
142
  }
132
143
 
133
144
  if (prepend) {
@@ -146,7 +157,8 @@ class CSSStyleSheetMock {
146
157
  }
147
158
  }
148
159
 
149
- const generateStyleTagFallback = (cssString, ref, { prepend = false } = {}) => new CSSStyleSheetMock(cssString, ref, { prepend });
160
+ const generateStyleTagFallback = (cssString, ref, { prepend = false } = {}) =>
161
+ new CSSStyleSheetMock(cssString, ref, { prepend });
150
162
 
151
163
  export const limitAbbreviation = (str, limit = 2) =>
152
164
  str
@@ -154,4 +166,4 @@ export const limitAbbreviation = (str, limit = 2) =>
154
166
  .split(' ')
155
167
  .splice(0, limit)
156
168
  .map((s) => s[0]?.toUpperCase())
157
- .join('');
169
+ .join('');
@@ -32,6 +32,7 @@ export const createDynamicDataMixin =
32
32
  slotName,
33
33
  rerenderAttrsList = [],
34
34
  targetSelector,
35
+ sortFn = (data) => data,
35
36
  }) =>
36
37
  (superclass) =>
37
38
  class DynamicDataMixinClass extends superclass {
@@ -62,7 +63,8 @@ export const createDynamicDataMixin =
62
63
 
63
64
  #renderItems() {
64
65
  this.#removeOldItems();
65
- this.data.forEach((item, index) => {
66
+ const items = sortFn ? this.data.sort(sortFn) : this.data;
67
+ items.forEach((item, index) => {
66
68
  const content = getTemplateContent(itemRenderer(item, index, this));
67
69
  if (!this.#targetEle) return;
68
70
  this.#targetEle.appendChild(content?.cloneNode(true));
@@ -102,5 +102,4 @@ export const createCssVarsList = (componentName, mappings) =>
102
102
 
103
103
  // on some cases we need a selector to be more specific than another
104
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('');
105
+ export const createClassSelectorSpecifier = (className, numOfRepeats) => `.${className}`.repeat(numOfRepeats);
@@ -100,18 +100,22 @@ export const createStyleMixin =
100
100
 
101
101
  #createOverridesStyle() {
102
102
  if (this.#styleAttributes.length) {
103
- const classSpecifier = createClassSelectorSpecifier(
103
+ let classSpecifier = createClassSelectorSpecifier(
104
104
  componentName,
105
- CSS_SELECTOR_SPECIFIER_MULTIPLY,
105
+ CSS_SELECTOR_SPECIFIER_MULTIPLY
106
106
  );
107
+ const elementId = this.getAttribute('id');
108
+ if (elementId) {
109
+ // basically this is enough to make the selector more specific
110
+ // but just in case there is no id, we will also add the class multiple times
111
+ classSpecifier += `#${elementId}`;
112
+ }
107
113
 
108
- this.#overrideStyleEle = injectStyle(
109
- `:host(${classSpecifier}) {}`,
110
- this.#rootElement,
111
- );
114
+ this.#overrideStyleEle = injectStyle(`:host(${classSpecifier}) {}`, this.#rootElement);
112
115
  }
113
116
  }
114
117
 
118
+
115
119
  #setAttrOverride(attrName, value) {
116
120
  const style = this.#overrideStyleEle?.cssRules[0].style;
117
121
 
package/src/constants.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export const DESCOPE_PREFIX = 'descope';
2
- export const CSS_SELECTOR_SPECIFIER_MULTIPLY = 3;
2
+ export const CSS_SELECTOR_SPECIFIER_MULTIPLY = 5;
3
3
  export const BASE_THEME_SECTION = 'host';
4
4
  export const PORTAL_THEME_PREFIX = '@';
@@ -71,6 +71,15 @@ export const globalsThemeToStyle = (theme, themeName = '') => {
71
71
  return `*[data-theme="${themeName}"] {${style}}`;
72
72
  };
73
73
 
74
+ function splitAmpersands(str) {
75
+ const match = str.match(/^(&+)?(.*)$/);
76
+ return [match[1] || "", (match[2] || "")];
77
+ }
78
+
79
+ // st attributes are also using selector multiplication
80
+ // so we need to limit the multiplication
81
+ const MAX_SELECTOR_MULTIPLY = 3;
82
+
74
83
  const componentsThemeToStyleObj = (componentsTheme) =>
75
84
  transformTheme(componentsTheme, [], (path, val) => {
76
85
  const [component, ...restPath] = path;
@@ -79,10 +88,7 @@ const componentsThemeToStyleObj = (componentsTheme) =>
79
88
 
80
89
  if (property === 'undefined') {
81
90
  // eslint-disable-next-line no-console
82
- console.warn(
83
- componentName,
84
- `theme value: "${val}" is mapped to an invalid property`,
85
- );
91
+ console.warn(componentName, `theme value: "${val}" is mapped to an invalid property`);
86
92
  }
87
93
 
88
94
  // we need a support for portal components theme (e.g. overlay)
@@ -98,23 +104,26 @@ const componentsThemeToStyleObj = (componentsTheme) =>
98
104
  // do not start with underscore -> key:value, must have 2 no underscore attrs in a row
99
105
  // starts with underscore -> attribute selector
100
106
  const attrsSelector = restPath.reduce((acc, section, idx) => {
101
- if (section.startsWith('_'))
102
- return `${acc}[${kebabCase(section.replace(/^_/, ''))}="true"]`;
107
+ const [ampersands, content] = splitAmpersands(section);
108
+ const selectorMultiplier = Math.min(
109
+ ampersands.length + 1, // if there are no & we need to multiply by 1
110
+ MAX_SELECTOR_MULTIPLY
111
+ );
112
+
113
+ if (content.startsWith('_')) return acc + `[${kebabCase(content.replace(/^_/, ''))}="true"]`.repeat(selectorMultiplier);
103
114
 
104
115
  const nextSection = restPath[idx + 1];
105
116
 
106
- if (typeof nextSection !== 'string' || nextSection.startsWith('_')) {
117
+ if (typeof nextSection !== 'string' || nextSection.startsWith('_') || nextSection.startsWith('&')) {
107
118
  // eslint-disable-next-line no-console
108
119
  console.error(
109
120
  'theme generator',
110
- `your theme structure is invalid, attribute "${section}" is followed by "${nextSection}" which is not allowed`,
121
+ `your theme structure is invalid, attribute "${section}" is followed by "${nextSection}" which is not allowed`
111
122
  );
112
123
  return acc;
113
124
  }
114
125
 
115
- return `${acc}[${kebabCase(section)}="${restPath
116
- .splice(idx + 1, 1)
117
- .join('')}"]`;
126
+ return acc + `[${kebabCase(content)}="${restPath.splice(idx + 1, 1).join('')}"]`.repeat(selectorMultiplier);
118
127
  }, '');
119
128
 
120
129
  const selector = `:host${attrsSelector ? `(${attrsSelector})` : ''}`;