@descope-ui/common 3.1.13 → 3.2.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
+ ## [3.2.1](https://github.com/descope/web-components-ui/compare/web-components-ui-3.2.0...web-components-ui-3.2.1) (2026-04-16)
6
+
7
+ ## [3.2.0](https://github.com/descope/web-components-ui/compare/web-components-ui-3.1.13...web-components-ui-3.2.0) (2026-04-16)
8
+
9
+
10
+ ### Features
11
+
12
+ * add descope-last-auth-badge component ([#982](https://github.com/descope/web-components-ui/issues/982)) ([055b461](https://github.com/descope/web-components-ui/commit/055b4618abbdf518494a2984e56d66e58fdb4809))
13
+
5
14
  ## [3.1.13](https://github.com/descope/web-components-ui/compare/web-components-ui-3.1.12...web-components-ui-3.1.13) (2026-04-15)
6
15
 
7
16
  ## [3.1.12](https://github.com/descope/web-components-ui/compare/web-components-ui-3.1.11...web-components-ui-3.1.12) (2026-04-15)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@descope-ui/common",
3
- "version": "3.1.13",
3
+ "version": "3.2.1",
4
4
  "dependencies": {
5
5
  "element-internals-polyfill": "^1.3.9",
6
6
  "color": "^4.2.3",
package/src/constants.js CHANGED
@@ -1,4 +1,3 @@
1
1
  export const DESCOPE_PREFIX = 'descope';
2
2
  export const CSS_SELECTOR_SPECIFIER_MULTIPLY = 5;
3
3
  export const BASE_THEME_SECTION = 'host';
4
- export const PORTAL_THEME_PREFIX = '@';
@@ -1,9 +1,5 @@
1
1
  import merge from 'lodash.merge';
2
- import {
3
- BASE_THEME_SECTION,
4
- DESCOPE_PREFIX,
5
- PORTAL_THEME_PREFIX,
6
- } from '../constants';
2
+ import { BASE_THEME_SECTION, DESCOPE_PREFIX } from '../constants';
7
3
  import { isUrl, kebabCase } from '../utils';
8
4
  import { getComponentName, getCssVarName } from '../componentsHelpers';
9
5
 
@@ -73,9 +69,16 @@ export const globalsThemeToStyle = (theme, themeName = '') => {
73
69
 
74
70
  function splitAmpersands(str) {
75
71
  const match = str.match(/^(&+)?(.*)$/);
76
- return [match[1] || "", (match[2] || "")];
72
+ return [match[1] || '', match[2] || ''];
77
73
  }
78
74
 
75
+ const CONTAINER_QUERY_PREFIX = '@container';
76
+ const isContainerQuery = (key) => key.startsWith(CONTAINER_QUERY_PREFIX);
77
+
78
+ const BREAKPOINTS_KEY = '$breakpoints';
79
+ const BREAKPOINTS_REF_PREFIX = '$breakpoints.';
80
+ const isBreakpointRef = (key) => key.startsWith(BREAKPOINTS_REF_PREFIX);
81
+
79
82
  // st attributes are also using selector multiplication
80
83
  // so we need to limit the multiplication
81
84
  const MAX_SELECTOR_MULTIPLY = 3;
@@ -83,70 +86,114 @@ const MAX_SELECTOR_MULTIPLY = 3;
83
86
  const componentsThemeToStyleObj = (componentsTheme) =>
84
87
  transformTheme(componentsTheme, [], (path, val) => {
85
88
  const [component, ...restPath] = path;
89
+
90
+ // skip $breakpoints definitions — they are metadata, not CSS output
91
+ if (restPath[0] === BREAKPOINTS_KEY) return {};
92
+
86
93
  const property = restPath.pop();
87
94
  const componentName = getComponentName(component);
88
95
 
89
96
  if (property === 'undefined') {
90
- // eslint-disable-next-line no-console
91
- console.warn(componentName, `theme value: "${val}" is mapped to an invalid property`);
97
+ console.warn(
98
+ componentName,
99
+ `theme value: "${val}" is mapped to an invalid property`,
100
+ );
92
101
  }
93
102
 
94
- // we need a support for portal components theme (e.g. overlay)
95
- // this allows us to generate those themes under different sections
96
- // if the theme has root level attribute that starts with #
97
- // we are generating a new theme
98
103
  let themeName = BASE_THEME_SECTION;
99
104
 
100
- if (restPath[0] && restPath[0].startsWith(PORTAL_THEME_PREFIX)) {
101
- themeName = restPath.shift();
105
+ // extract @container queries from restPath at any position,
106
+ // resolving breakpoints.X references via the component's $breakpoints map
107
+ const componentBreakpoints =
108
+ componentsTheme[component]?.[BREAKPOINTS_KEY] || {};
109
+ const resolveKey = (key) =>
110
+ isBreakpointRef(key)
111
+ ? `${CONTAINER_QUERY_PREFIX} ${componentBreakpoints[key.slice(BREAKPOINTS_REF_PREFIX.length)]}`
112
+ : key;
113
+ const breakpointRefs = restPath.filter(isBreakpointRef);
114
+ const missingRef = breakpointRefs.find(
115
+ (key) => !componentBreakpoints[key.slice(BREAKPOINTS_REF_PREFIX.length)],
116
+ );
117
+ if (missingRef) {
118
+ console.warn(
119
+ componentName,
120
+ `theme references undefined breakpoint "${missingRef}"`,
121
+ );
122
+ return {};
102
123
  }
124
+ const containerQueries = breakpointRefs.map(resolveKey);
125
+ // remove breakpoint refs from restPath so the remaining parts are processed as attribute selectors
126
+ restPath.splice(
127
+ 0,
128
+ restPath.length,
129
+ ...restPath.filter((s) => !isBreakpointRef(s)),
130
+ );
103
131
 
104
132
  // do not start with underscore -> key:value, must have 2 no underscore attrs in a row
105
133
  // starts with underscore -> attribute selector
106
134
  const attrsSelector = restPath.reduce((acc, section, idx) => {
107
135
  const [ampersands, content] = splitAmpersands(section);
108
136
  const selectorMultiplier = Math.min(
109
- ampersands.length + 1, // if there are no & we need to multiply by 1
110
- MAX_SELECTOR_MULTIPLY
137
+ ampersands.length + 1, // if there are no & we need to multiply by 1
138
+ MAX_SELECTOR_MULTIPLY,
111
139
  );
112
140
 
113
- if (content.startsWith('_')) return acc + `[${kebabCase(content.replace(/^_/, ''))}="true"]`.repeat(selectorMultiplier);
141
+ if (content.startsWith('_'))
142
+ return (
143
+ acc +
144
+ `[${kebabCase(content.replace(/^_/, ''))}="true"]`.repeat(
145
+ selectorMultiplier,
146
+ )
147
+ );
114
148
 
115
149
  const nextSection = restPath[idx + 1];
116
150
 
117
- if (typeof nextSection !== 'string' || nextSection.startsWith('_') || nextSection.startsWith('&')) {
118
- // eslint-disable-next-line no-console
151
+ if (
152
+ typeof nextSection !== 'string' ||
153
+ nextSection.startsWith('_') ||
154
+ nextSection.startsWith('&')
155
+ ) {
119
156
  console.error(
120
157
  'theme generator',
121
- `your theme structure is invalid, attribute "${section}" is followed by "${nextSection}" which is not allowed`
158
+ `your theme structure is invalid, attribute "${section}" is followed by "${nextSection}" which is not allowed`,
122
159
  );
123
160
  return acc;
124
161
  }
125
162
 
126
- return acc + `[${kebabCase(content)}="${restPath.splice(idx + 1, 1).join('')}"]`.repeat(selectorMultiplier);
163
+ return (
164
+ acc +
165
+ `[${kebabCase(content)}="${restPath.splice(idx + 1, 1).join('')}"]`.repeat(
166
+ selectorMultiplier,
167
+ )
168
+ );
127
169
  }, '');
128
170
 
129
171
  const selector = `:host${attrsSelector ? `(${attrsSelector})` : ''}`;
130
172
 
173
+ // wrap the selector rule in container queries from innermost to outermost
174
+ // e.g. ['@container (max-width: 80px)'] + { ':host': { prop: val } }
175
+ // => { '@container (max-width: 80px)': { ':host': { prop: val } } }
176
+ const leaf = containerQueries.reduceRight(
177
+ (acc, query) => ({ [query]: acc }),
178
+ { [selector]: { [property]: getCssVarValue(val) } },
179
+ );
180
+
131
181
  return {
132
182
  [componentName]: {
133
- [themeName]: {
134
- [selector]: {
135
- [property]: getCssVarValue(val),
136
- },
137
- },
183
+ [themeName]: leaf,
138
184
  },
139
185
  };
140
186
  });
141
187
 
142
188
  const componentsThemeToStyle = (componentsTheme) =>
143
- Object.entries(componentsTheme).reduce(
144
- (acc, [selector, vars]) =>
145
- `${acc}${selector} { \n${Object.entries(vars)
146
- .map(([key, val]) => `${key}: ${val}`)
147
- .join(';\n')} \n}\n\n`,
148
- '',
149
- );
189
+ Object.entries(componentsTheme).reduce((acc, [key, value]) => {
190
+ if (isContainerQuery(key)) {
191
+ return `${acc}${key} {\n${componentsThemeToStyle(value)}}\n\n`;
192
+ }
193
+ return `${acc}${key} { \n${Object.entries(value)
194
+ .map(([prop, val]) => `${prop}: ${val}`)
195
+ .join(';\n')} \n}\n\n`;
196
+ }, '');
150
197
 
151
198
  export const createComponentsTheme = (componentsTheme) => {
152
199
  const styleObj = componentsThemeToStyleObj(componentsTheme);