@pure-ds/core 0.5.33 → 0.5.35
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/dist/types/public/assets/pds/components/pds-icon.d.ts +11 -0
- package/dist/types/public/assets/pds/components/pds-icon.d.ts.map +1 -1
- package/dist/types/public/assets/pds/components/pds-omnibox.d.ts +3 -0
- package/dist/types/public/assets/pds/components/pds-omnibox.d.ts.map +1 -1
- package/package.json +1 -1
- package/packages/pds-cli/bin/pds-bootstrap.js +11 -7
- package/public/assets/pds/components/pds-icon.js +315 -40
- package/public/assets/pds/components/pds-omnibox.js +203 -9
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* @attr {string} sprite - Override sprite sheet path
|
|
11
11
|
* @attr {number} rotate - Rotation angle in degrees
|
|
12
12
|
* @attr {boolean} no-sprite - Force fallback icon rendering
|
|
13
|
+
* @attr {boolean} morph - Morph the icon when the icon name changes
|
|
13
14
|
*
|
|
14
15
|
* @example
|
|
15
16
|
* <pds-icon icon="house"></pds-icon>
|
|
@@ -51,6 +52,16 @@ export class SvgIcon extends HTMLElement {
|
|
|
51
52
|
static fetchExternalIcon(iconName: string): Promise<boolean>;
|
|
52
53
|
static ensureInlineSprite(spriteURL: any): Promise<any>;
|
|
53
54
|
static notifyInstances(): void;
|
|
55
|
+
_currentIcon: any;
|
|
56
|
+
_pendingIcon: any;
|
|
57
|
+
_morphing: boolean;
|
|
58
|
+
_morphTimer: any;
|
|
59
|
+
_templateReady: boolean;
|
|
60
|
+
_stackEl: Element;
|
|
61
|
+
_svgOldEl: Element;
|
|
62
|
+
_svgNewEl: Element;
|
|
63
|
+
_iconOldGroupEl: Element;
|
|
64
|
+
_iconNewGroupEl: Element;
|
|
54
65
|
connectedCallback(): void;
|
|
55
66
|
disconnectedCallback(): void;
|
|
56
67
|
attributeChangedCallback(name: any, oldValue: any, newValue: any): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pds-icon.d.ts","sourceRoot":"","sources":["../../../../../../public/assets/pds/components/pds-icon.js"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"pds-icon.d.ts","sourceRoot":"","sources":["../../../../../../public/assets/pds/components/pds-icon.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;IACE,oCAAkF;IAGlF;;;;;;;;;;MAkBE;IAEF,qCAAkC;IAClC,oCAAiC;IAGjC,wCAAqC;IAErC,2CAAwC;IAExC,2BAA6B;IAE7B,4CAA8B;IAO9B,oDA2CC;IAqdD;;;;OAIG;IACH,mCAmBC;IAED;;;;OAIG;IACH,mCAHW,MAAM,GACJ,OAAO,CAAC,OAAO,CAAC,CA6E5B;IAED,wDA8EC;IACD,+BAMC;IAjpBC,kBAAwB;IACxB,kBAAwB;IACxB,mBAAsB;IACtB,iBAAuB;IACvB,wBAA2B;IAC3B,kBAAoB;IACpB,mBAAqB;IACrB,mBAAqB;IACrB,yBAA2B;IAC3B,yBAA2B;IAG7B,0BAGC;IAED,6BAGC;IAED,wEAmBC;IAED,eAiQC;IA0JD;;;;;OAKG;IACH,0BAFa,OAAO,CAInB;;CAoMF"}
|
|
@@ -2,6 +2,7 @@ export class PdsOmnibox extends HTMLElement {
|
|
|
2
2
|
static formAssociated: boolean;
|
|
3
3
|
static get observedAttributes(): string[];
|
|
4
4
|
connectedCallback(): void;
|
|
5
|
+
disconnectedCallback(): void;
|
|
5
6
|
attributeChangedCallback(name: any, oldValue: any, newValue: any): void;
|
|
6
7
|
set settings(value: any);
|
|
7
8
|
get settings(): any;
|
|
@@ -17,6 +18,8 @@ export class PdsOmnibox extends HTMLElement {
|
|
|
17
18
|
get required(): boolean;
|
|
18
19
|
set autocomplete(value: string);
|
|
19
20
|
get autocomplete(): string;
|
|
21
|
+
set icon(value: string);
|
|
22
|
+
get icon(): string;
|
|
20
23
|
formAssociatedCallback(): void;
|
|
21
24
|
formDisabledCallback(disabled: any): void;
|
|
22
25
|
formResetCallback(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pds-omnibox.d.ts","sourceRoot":"","sources":["../../../../../../public/assets/pds/components/pds-omnibox.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pds-omnibox.d.ts","sourceRoot":"","sources":["../../../../../../public/assets/pds/components/pds-omnibox.js"],"names":[],"mappings":"AAmBA;IACC,+BAA6B;IAE7B,0CAEC;IAuBD,0BAUC;IAED,6BAOC;IAED,wEAGC;IAMD,yBAEC;IAND,oBAEC;IAUD,wBAGC;IAPD,mBAEC;IAWD,+BAGC;IAPD,0BAEC;IAWD,sBAIC;IARD,iBAEC;IAYD,6BAGC;IAPD,wBAEC;IAWD,6BAGC;IAPD,wBAEC;IAWD,gCAGC;IAPD,2BAEC;IAWD,wBAGC;IAPD,mBAEC;IAOD,+BAA2B;IAE3B,0CAGC;IAED,0BAEC;IAED,2CAEC;IAED,qBAEC;IAED,sBAEC;;CAqcD"}
|
package/package.json
CHANGED
|
@@ -104,12 +104,11 @@ async function copyTemplateDirectory(sourceDir, targetDir, options) {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
async function copyBootstrapTemplate({ version, generatedAt
|
|
107
|
+
async function copyBootstrapTemplate({ version, generatedAt }) {
|
|
108
108
|
await copyTemplateDirectory(templateRoot, projectRoot, { version, generatedAt });
|
|
109
109
|
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
const esbuildTarget = path.join(projectRoot, 'esbuild-dev.js');
|
|
110
|
+
const esbuildSource = path.join(templateRoot, 'esbuild-dev.cjs');
|
|
111
|
+
const esbuildTarget = path.join(projectRoot, 'esbuild-dev.cjs');
|
|
113
112
|
await copyFileIfMissing(esbuildSource, esbuildTarget);
|
|
114
113
|
}
|
|
115
114
|
|
|
@@ -118,7 +117,13 @@ async function ensurePackageScripts(pkg, pkgPath) {
|
|
|
118
117
|
let changed = false;
|
|
119
118
|
|
|
120
119
|
if (!pkg.scripts.dev) {
|
|
121
|
-
|
|
120
|
+
if (existsSync(path.join(projectRoot, 'esbuild-dev.cjs'))) {
|
|
121
|
+
pkg.scripts.dev = 'node esbuild-dev.cjs';
|
|
122
|
+
} else if (existsSync(path.join(projectRoot, 'esbuild-dev.js'))) {
|
|
123
|
+
pkg.scripts.dev = 'node esbuild-dev.js';
|
|
124
|
+
} else {
|
|
125
|
+
pkg.scripts.dev = 'node esbuild-dev.cjs';
|
|
126
|
+
}
|
|
122
127
|
changed = true;
|
|
123
128
|
}
|
|
124
129
|
|
|
@@ -262,7 +267,6 @@ async function main() {
|
|
|
262
267
|
log('\n⚡ PDS Bootstrap\n');
|
|
263
268
|
|
|
264
269
|
const { pkgPath, pkg } = await readPackageJson();
|
|
265
|
-
const isModule = pkg.type === 'module';
|
|
266
270
|
|
|
267
271
|
await ensurePackageScripts(pkg, pkgPath);
|
|
268
272
|
await ensureEsbuildDependency(pkg, pkgPath);
|
|
@@ -270,7 +274,7 @@ async function main() {
|
|
|
270
274
|
const version = await getPdsCoreVersion();
|
|
271
275
|
const generatedAt = new Date().toLocaleString();
|
|
272
276
|
|
|
273
|
-
await copyBootstrapTemplate({ version, generatedAt
|
|
277
|
+
await copyBootstrapTemplate({ version, generatedAt });
|
|
274
278
|
|
|
275
279
|
await ensurePdsAssets();
|
|
276
280
|
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* @attr {string} sprite - Override sprite sheet path
|
|
11
11
|
* @attr {number} rotate - Rotation angle in degrees
|
|
12
12
|
* @attr {boolean} no-sprite - Force fallback icon rendering
|
|
13
|
+
* @attr {boolean} morph - Morph the icon when the icon name changes
|
|
13
14
|
*
|
|
14
15
|
* @example
|
|
15
16
|
* <pds-icon icon="house"></pds-icon>
|
|
@@ -19,7 +20,7 @@
|
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
22
|
export class SvgIcon extends HTMLElement {
|
|
22
|
-
static observedAttributes = ['icon', 'size', 'color', 'label', 'rotate'];
|
|
23
|
+
static observedAttributes = ['icon', 'size', 'color', 'label', 'rotate', 'morph'];
|
|
23
24
|
|
|
24
25
|
// Inline fallback icons for critical UI elements (when sprite fails to load)
|
|
25
26
|
static #fallbackIcons = {
|
|
@@ -107,6 +108,16 @@ export class SvgIcon extends HTMLElement {
|
|
|
107
108
|
constructor() {
|
|
108
109
|
super();
|
|
109
110
|
this.attachShadow({ mode: 'open' });
|
|
111
|
+
this._currentIcon = null;
|
|
112
|
+
this._pendingIcon = null;
|
|
113
|
+
this._morphing = false;
|
|
114
|
+
this._morphTimer = null;
|
|
115
|
+
this._templateReady = false;
|
|
116
|
+
this._stackEl = null;
|
|
117
|
+
this._svgOldEl = null;
|
|
118
|
+
this._svgNewEl = null;
|
|
119
|
+
this._iconOldGroupEl = null;
|
|
120
|
+
this._iconNewGroupEl = null;
|
|
110
121
|
}
|
|
111
122
|
|
|
112
123
|
connectedCallback() {
|
|
@@ -116,16 +127,33 @@ export class SvgIcon extends HTMLElement {
|
|
|
116
127
|
|
|
117
128
|
disconnectedCallback() {
|
|
118
129
|
SvgIcon.instances.delete(this);
|
|
130
|
+
this.#clearMorphTimers();
|
|
119
131
|
}
|
|
120
132
|
|
|
121
133
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
122
134
|
if (oldValue !== newValue) {
|
|
135
|
+
if (name === 'icon') {
|
|
136
|
+
if (this.hasAttribute('morph')) {
|
|
137
|
+
this.#startMorph(newValue, oldValue);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
this._currentIcon = newValue;
|
|
141
|
+
this._pendingIcon = null;
|
|
142
|
+
this._morphing = false;
|
|
143
|
+
this.#clearMorphTimers();
|
|
144
|
+
this.render();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (name === 'morph') {
|
|
148
|
+
this.#clearMorphTimers();
|
|
149
|
+
}
|
|
123
150
|
this.render();
|
|
124
151
|
}
|
|
125
152
|
}
|
|
126
153
|
|
|
127
154
|
render() {
|
|
128
|
-
const
|
|
155
|
+
const attrIcon = this.getAttribute('icon') || 'missing';
|
|
156
|
+
const icon = this._morphing ? (this._currentIcon || attrIcon) : attrIcon;
|
|
129
157
|
const sizeAttr = this.getAttribute('size') || '24';
|
|
130
158
|
const color = this.getAttribute('color') || 'currentColor';
|
|
131
159
|
const label = this.getAttribute('label');
|
|
@@ -235,52 +263,299 @@ export class SvgIcon extends HTMLElement {
|
|
|
235
263
|
const transform = rotate !== '0' ? `rotate(${rotate} 128 128)` : '';
|
|
236
264
|
const defaultViewBox = '0 0 256 256';
|
|
237
265
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
266
|
+
const resolveIconData = (iconName) => {
|
|
267
|
+
let effectiveHrefLocal = spriteHref ? `${spriteHref}#${iconName}` : `#${iconName}`;
|
|
268
|
+
let inlineSymbolContentLocal = null;
|
|
269
|
+
let inlineSymbolViewBoxLocal = null;
|
|
270
|
+
let inlineSymbolPreserveAspectRatioLocal = null;
|
|
271
|
+
let useExternalIconLocal = false;
|
|
272
|
+
let externalIconDataLocal = null;
|
|
273
|
+
let useFallbackLocal = this.hasAttribute('no-sprite') || !this.spriteAvailable();
|
|
274
|
+
let spriteIconNotFoundLocal = false;
|
|
275
|
+
let spriteStillLoadingLocal = false;
|
|
276
|
+
|
|
277
|
+
if (!useFallbackLocal && typeof window !== 'undefined' && spriteHref) {
|
|
278
|
+
try {
|
|
279
|
+
const spriteURL = new URL(spriteHref, window.location.href);
|
|
280
|
+
const spriteKey = spriteURL.href;
|
|
281
|
+
const inlineSpriteData = SvgIcon.inlineSprites.get(spriteKey);
|
|
282
|
+
|
|
283
|
+
if (inlineSpriteData && inlineSpriteData.loaded) {
|
|
284
|
+
const symbolData = inlineSpriteData.symbols.get(iconName);
|
|
285
|
+
if (symbolData) {
|
|
286
|
+
inlineSymbolContentLocal = symbolData.content;
|
|
287
|
+
inlineSymbolViewBoxLocal = symbolData.viewBox;
|
|
288
|
+
inlineSymbolPreserveAspectRatioLocal = symbolData.preserveAspectRatio;
|
|
289
|
+
} else {
|
|
290
|
+
spriteIconNotFoundLocal = true;
|
|
291
|
+
}
|
|
292
|
+
} else if (inlineSpriteData && inlineSpriteData.error) {
|
|
293
|
+
spriteIconNotFoundLocal = true;
|
|
294
|
+
} else {
|
|
295
|
+
SvgIcon.ensureInlineSprite(spriteKey);
|
|
296
|
+
spriteStillLoadingLocal = true;
|
|
297
|
+
useFallbackLocal = true;
|
|
298
|
+
}
|
|
299
|
+
} catch (e) {
|
|
300
|
+
// Ignore URL errors and fall back to default behaviour
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const hasFallbackLocal = SvgIcon.#fallbackIcons.hasOwnProperty(iconName);
|
|
305
|
+
if (spriteIconNotFoundLocal && !hasFallbackLocal && !spriteStillLoadingLocal) {
|
|
306
|
+
const cached = SvgIcon.externalIconCache.get(iconName);
|
|
307
|
+
if (cached) {
|
|
308
|
+
if (cached.loaded && cached.content) {
|
|
309
|
+
useExternalIconLocal = true;
|
|
310
|
+
externalIconDataLocal = cached;
|
|
311
|
+
} else if (cached.error) {
|
|
312
|
+
useFallbackLocal = true;
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
SvgIcon.fetchExternalIcon(iconName);
|
|
316
|
+
useFallbackLocal = true;
|
|
317
|
+
}
|
|
246
318
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
preserveAspectRatioAttr = ` preserveAspectRatio="${inlineSymbolPreserveAspectRatio}"`;
|
|
319
|
+
|
|
320
|
+
if (spriteIconNotFoundLocal && !useExternalIconLocal) {
|
|
321
|
+
useFallbackLocal = true;
|
|
251
322
|
}
|
|
252
|
-
}
|
|
253
323
|
|
|
254
|
-
|
|
324
|
+
let viewBoxLocal = defaultViewBox;
|
|
325
|
+
let preserveAspectRatioLocal = '';
|
|
326
|
+
|
|
327
|
+
if (useExternalIconLocal && externalIconDataLocal) {
|
|
328
|
+
viewBoxLocal = externalIconDataLocal.viewBox || '0 0 24 24';
|
|
329
|
+
if (externalIconDataLocal.preserveAspectRatio) {
|
|
330
|
+
preserveAspectRatioLocal = externalIconDataLocal.preserveAspectRatio;
|
|
331
|
+
}
|
|
332
|
+
} else if (inlineSymbolViewBoxLocal) {
|
|
333
|
+
viewBoxLocal = inlineSymbolViewBoxLocal;
|
|
334
|
+
if (inlineSymbolPreserveAspectRatioLocal) {
|
|
335
|
+
preserveAspectRatioLocal = inlineSymbolPreserveAspectRatioLocal;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const hasInlineSymbolLocal = inlineSymbolContentLocal !== null;
|
|
340
|
+
|
|
341
|
+
let symbolMarkupLocal;
|
|
342
|
+
if (useExternalIconLocal && externalIconDataLocal?.content) {
|
|
343
|
+
symbolMarkupLocal = externalIconDataLocal.content;
|
|
344
|
+
} else if (useFallbackLocal) {
|
|
345
|
+
symbolMarkupLocal = this.#getFallbackIcon(iconName);
|
|
346
|
+
} else if (hasInlineSymbolLocal) {
|
|
347
|
+
symbolMarkupLocal = inlineSymbolContentLocal;
|
|
348
|
+
} else {
|
|
349
|
+
symbolMarkupLocal = `<use href="${effectiveHrefLocal}"></use>`;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
symbolMarkup: symbolMarkupLocal,
|
|
354
|
+
viewBox: viewBoxLocal,
|
|
355
|
+
preserveAspectRatio: preserveAspectRatioLocal,
|
|
356
|
+
};
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const currentData = resolveIconData(icon);
|
|
360
|
+
const nextIcon = this._morphing ? (this._pendingIcon || attrIcon) : attrIcon;
|
|
361
|
+
const nextData = resolveIconData(nextIcon);
|
|
255
362
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
|
|
363
|
+
const morphClass = this._morphing ? 'morphing' : '';
|
|
364
|
+
|
|
365
|
+
this.#ensureTemplate();
|
|
366
|
+
|
|
367
|
+
if (!this._stackEl || !this._svgOldEl || !this._svgNewEl || !this._iconOldGroupEl || !this._iconNewGroupEl) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
this._stackEl.setAttribute('class', `icon-stack${morphClass ? ` ${morphClass}` : ''}`);
|
|
372
|
+
this._stackEl.style.width = `${size}px`;
|
|
373
|
+
this._stackEl.style.height = `${size}px`;
|
|
374
|
+
|
|
375
|
+
this._svgOldEl.setAttribute('width', size);
|
|
376
|
+
this._svgOldEl.setAttribute('height', size);
|
|
377
|
+
this._svgOldEl.setAttribute('fill', color);
|
|
378
|
+
this._svgOldEl.setAttribute('viewBox', currentData.viewBox);
|
|
379
|
+
if (currentData.preserveAspectRatio) {
|
|
380
|
+
this._svgOldEl.setAttribute('preserveAspectRatio', currentData.preserveAspectRatio);
|
|
264
381
|
} else {
|
|
265
|
-
|
|
382
|
+
this._svgOldEl.removeAttribute('preserveAspectRatio');
|
|
266
383
|
}
|
|
267
|
-
|
|
384
|
+
|
|
385
|
+
this._svgNewEl.setAttribute('width', size);
|
|
386
|
+
this._svgNewEl.setAttribute('height', size);
|
|
387
|
+
this._svgNewEl.setAttribute('fill', color);
|
|
388
|
+
this._svgNewEl.setAttribute('viewBox', nextData.viewBox);
|
|
389
|
+
if (nextData.preserveAspectRatio) {
|
|
390
|
+
this._svgNewEl.setAttribute('preserveAspectRatio', nextData.preserveAspectRatio);
|
|
391
|
+
} else {
|
|
392
|
+
this._svgNewEl.removeAttribute('preserveAspectRatio');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (label) {
|
|
396
|
+
this._svgNewEl.setAttribute('role', 'img');
|
|
397
|
+
this._svgNewEl.setAttribute('aria-label', label);
|
|
398
|
+
this._svgNewEl.setAttribute('aria-hidden', 'false');
|
|
399
|
+
} else {
|
|
400
|
+
this._svgNewEl.setAttribute('aria-hidden', 'true');
|
|
401
|
+
this._svgNewEl.removeAttribute('role');
|
|
402
|
+
this._svgNewEl.removeAttribute('aria-label');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
this._svgOldEl.setAttribute('aria-hidden', 'true');
|
|
406
|
+
|
|
407
|
+
this._iconOldGroupEl.setAttribute('transform', transform);
|
|
408
|
+
this._iconOldGroupEl.innerHTML = currentData.symbolMarkup;
|
|
409
|
+
this._iconNewGroupEl.setAttribute('transform', transform);
|
|
410
|
+
this._iconNewGroupEl.innerHTML = nextData.symbolMarkup;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
#ensureTemplate() {
|
|
414
|
+
if (this._templateReady) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
268
418
|
this.shadowRoot.innerHTML = `
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
419
|
+
<style>
|
|
420
|
+
:host {
|
|
421
|
+
--pds-icon-morph-duration: 300ms;
|
|
422
|
+
--pds-icon-morph-half-duration: calc(var(--pds-icon-morph-duration) / 2);
|
|
423
|
+
--pds-icon-morph-rotate: 12deg;
|
|
424
|
+
--pds-icon-morph-blur: 10px;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.icon-stack {
|
|
428
|
+
display: inline-block;
|
|
429
|
+
position: relative;
|
|
430
|
+
line-height: 0;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.icon-stack svg.pds-icon {
|
|
434
|
+
position: absolute;
|
|
435
|
+
inset: 0;
|
|
436
|
+
display: block;
|
|
437
|
+
width: 100%;
|
|
438
|
+
height: 100%;
|
|
439
|
+
will-change: transform, opacity, filter;
|
|
440
|
+
transform-origin: 50% 50%;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.icon-stack .layer-old {
|
|
444
|
+
opacity: 0;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.icon-stack .layer-new {
|
|
448
|
+
opacity: 1;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.icon-stack.morphing .layer-old {
|
|
452
|
+
opacity: 1;
|
|
453
|
+
animation: pds-icon-morph-out var(--pds-icon-morph-duration) ease-in-out;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.icon-stack.morphing .layer-new {
|
|
457
|
+
opacity: 0;
|
|
458
|
+
animation: pds-icon-morph-in var(--pds-icon-morph-duration) ease-in-out;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
@keyframes pds-icon-morph-out {
|
|
462
|
+
0% { opacity: 1; filter: blur(0); transform: rotate(0deg); }
|
|
463
|
+
100% { opacity: 0; filter: blur(var(--pds-icon-morph-blur)); transform: rotate(var(--pds-icon-morph-rotate)); }
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
@keyframes pds-icon-morph-in {
|
|
467
|
+
0% { opacity: 0; filter: blur(var(--pds-icon-morph-blur)); transform: rotate(calc(var(--pds-icon-morph-rotate) * -1)); }
|
|
468
|
+
100% { opacity: 1; filter: blur(0); transform: rotate(0deg); }
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
@media (prefers-reduced-motion: reduce) {
|
|
472
|
+
.icon-stack.morphing .layer-old,
|
|
473
|
+
.icon-stack.morphing .layer-new {
|
|
474
|
+
animation: none;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
</style>
|
|
478
|
+
<span class="icon-stack">
|
|
479
|
+
<svg class="pds-icon layer-old" aria-hidden="true">
|
|
480
|
+
<g id="icon-group-old"></g>
|
|
481
|
+
</svg>
|
|
482
|
+
<svg class="pds-icon layer-new" aria-hidden="true">
|
|
483
|
+
<g id="icon-group-new"></g>
|
|
484
|
+
</svg>
|
|
485
|
+
</span>
|
|
283
486
|
`;
|
|
487
|
+
|
|
488
|
+
this._stackEl = this.shadowRoot.querySelector('.icon-stack');
|
|
489
|
+
this._svgOldEl = this.shadowRoot.querySelector('svg.layer-old');
|
|
490
|
+
this._svgNewEl = this.shadowRoot.querySelector('svg.layer-new');
|
|
491
|
+
this._iconOldGroupEl = this.shadowRoot.querySelector('#icon-group-old');
|
|
492
|
+
this._iconNewGroupEl = this.shadowRoot.querySelector('#icon-group-new');
|
|
493
|
+
this._templateReady = true;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
#clearMorphTimers() {
|
|
497
|
+
if (this._morphTimer) {
|
|
498
|
+
clearTimeout(this._morphTimer);
|
|
499
|
+
this._morphTimer = null;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
#startMorph(newIcon, oldIcon) {
|
|
504
|
+
if (!oldIcon) {
|
|
505
|
+
this._currentIcon = newIcon;
|
|
506
|
+
this._pendingIcon = null;
|
|
507
|
+
this._morphing = false;
|
|
508
|
+
this.render();
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
this.#clearMorphTimers();
|
|
513
|
+
|
|
514
|
+
this._currentIcon = oldIcon;
|
|
515
|
+
this._pendingIcon = newIcon;
|
|
516
|
+
this._morphing = false;
|
|
517
|
+
this.render();
|
|
518
|
+
|
|
519
|
+
requestAnimationFrame(() => {
|
|
520
|
+
if (!this.isConnected) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
this._morphing = true;
|
|
525
|
+
this.render();
|
|
526
|
+
|
|
527
|
+
const totalDuration = this.#getMorphDurationMs();
|
|
528
|
+
const halfDuration = totalDuration / 2;
|
|
529
|
+
|
|
530
|
+
this._morphTimer = setTimeout(() => {
|
|
531
|
+
this._currentIcon = this._pendingIcon || newIcon;
|
|
532
|
+
this._pendingIcon = null;
|
|
533
|
+
this.render();
|
|
534
|
+
this._morphTimer = setTimeout(() => {
|
|
535
|
+
this._morphing = false;
|
|
536
|
+
this.render();
|
|
537
|
+
}, halfDuration);
|
|
538
|
+
}, halfDuration);
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
#getMorphDurationMs() {
|
|
543
|
+
try {
|
|
544
|
+
const value = getComputedStyle(this).getPropertyValue('--pds-icon-morph-duration').trim();
|
|
545
|
+
if (value) {
|
|
546
|
+
if (value.endsWith('ms')) {
|
|
547
|
+
const ms = Number.parseFloat(value);
|
|
548
|
+
return Number.isFinite(ms) ? ms : 200;
|
|
549
|
+
}
|
|
550
|
+
if (value.endsWith('s')) {
|
|
551
|
+
const seconds = Number.parseFloat(value);
|
|
552
|
+
return Number.isFinite(seconds) ? seconds * 1000 : 200;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
} catch (error) {
|
|
556
|
+
// ignore and fall back
|
|
557
|
+
}
|
|
558
|
+
return 200;
|
|
284
559
|
}
|
|
285
560
|
|
|
286
561
|
#getFallbackIcon(name) {
|
|
@@ -15,19 +15,27 @@
|
|
|
15
15
|
*/
|
|
16
16
|
const LAYERS = ["tokens", "primitives", "components", "utilities"];
|
|
17
17
|
const DEFAULT_PLACEHOLDER = "Search...";
|
|
18
|
+
const DEFAULT_ICON = "magnifying-glass";
|
|
18
19
|
|
|
19
20
|
export class PdsOmnibox extends HTMLElement {
|
|
20
21
|
static formAssociated = true;
|
|
21
22
|
|
|
22
23
|
static get observedAttributes() {
|
|
23
|
-
return ["name", "placeholder", "value", "disabled", "required", "autocomplete"];
|
|
24
|
+
return ["name", "placeholder", "value", "disabled", "required", "autocomplete", "icon"];
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
#root;
|
|
27
28
|
#internals;
|
|
28
29
|
#input;
|
|
30
|
+
#icon;
|
|
29
31
|
#settings;
|
|
30
32
|
#defaultValue = "";
|
|
33
|
+
#autoCompleteResizeHandler;
|
|
34
|
+
#autoCompleteScrollHandler;
|
|
35
|
+
#autoCompleteViewportHandler;
|
|
36
|
+
#lengthProbe;
|
|
37
|
+
#suggestionsUpdatedHandler;
|
|
38
|
+
#suggestionsObserver;
|
|
31
39
|
|
|
32
40
|
constructor() {
|
|
33
41
|
super();
|
|
@@ -41,6 +49,21 @@ export class PdsOmnibox extends HTMLElement {
|
|
|
41
49
|
this.#defaultValue = this.getAttribute("value") || "";
|
|
42
50
|
this.#syncAttributes();
|
|
43
51
|
this.#updateFormValue(this.#input.value || "");
|
|
52
|
+
if (!this.#suggestionsUpdatedHandler) {
|
|
53
|
+
this.#suggestionsUpdatedHandler = (event) => {
|
|
54
|
+
this.#handleSuggestionsUpdated(event);
|
|
55
|
+
};
|
|
56
|
+
this.addEventListener("suggestions-updated", this.#suggestionsUpdatedHandler);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
disconnectedCallback() {
|
|
61
|
+
this.#teardownAutoCompleteSizing();
|
|
62
|
+
this.#teardownSuggestionsObserver();
|
|
63
|
+
if (this.#suggestionsUpdatedHandler) {
|
|
64
|
+
this.removeEventListener("suggestions-updated", this.#suggestionsUpdatedHandler);
|
|
65
|
+
this.#suggestionsUpdatedHandler = null;
|
|
66
|
+
}
|
|
44
67
|
}
|
|
45
68
|
|
|
46
69
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -54,7 +77,6 @@ export class PdsOmnibox extends HTMLElement {
|
|
|
54
77
|
|
|
55
78
|
set settings(value) {
|
|
56
79
|
this.#settings = value;
|
|
57
|
-
console.log('settings set', this.#settings);
|
|
58
80
|
}
|
|
59
81
|
|
|
60
82
|
get name() {
|
|
@@ -112,6 +134,15 @@ export class PdsOmnibox extends HTMLElement {
|
|
|
112
134
|
else this.setAttribute("autocomplete", value);
|
|
113
135
|
}
|
|
114
136
|
|
|
137
|
+
get icon() {
|
|
138
|
+
return this.getAttribute("icon") || DEFAULT_ICON;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
set icon(value) {
|
|
142
|
+
if (value == null || value === "") this.removeAttribute("icon");
|
|
143
|
+
else this.setAttribute("icon", value);
|
|
144
|
+
}
|
|
145
|
+
|
|
115
146
|
formAssociatedCallback() {}
|
|
116
147
|
|
|
117
148
|
formDisabledCallback(disabled) {
|
|
@@ -138,14 +169,20 @@ export class PdsOmnibox extends HTMLElement {
|
|
|
138
169
|
#renderStructure() {
|
|
139
170
|
this.#root.innerHTML = `
|
|
140
171
|
<div class="ac-container input-icon">
|
|
141
|
-
<pds-icon icon="
|
|
172
|
+
<pds-icon morph icon="${DEFAULT_ICON}"></pds-icon>
|
|
142
173
|
<input class="ac-input" type="search" placeholder="${DEFAULT_PLACEHOLDER}" autocomplete="off" />
|
|
143
174
|
</div>
|
|
144
175
|
`;
|
|
145
176
|
|
|
177
|
+
this.#lengthProbe = document.createElement("div");
|
|
178
|
+
this.#lengthProbe.style.cssText = "position:absolute; visibility:hidden; width:0; height:0; pointer-events:none;";
|
|
179
|
+
this.#root.appendChild(this.#lengthProbe);
|
|
180
|
+
|
|
146
181
|
this.#input = this.#root.querySelector("input");
|
|
182
|
+
this.#icon = this.#root.querySelector("pds-icon");
|
|
147
183
|
this.#input.addEventListener("input", () => {
|
|
148
184
|
this.#updateFormValue(this.#input.value);
|
|
185
|
+
this.#updateSuggestionMaxHeight();
|
|
149
186
|
this.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
|
|
150
187
|
});
|
|
151
188
|
this.#input.addEventListener("change", () => {
|
|
@@ -154,6 +191,15 @@ export class PdsOmnibox extends HTMLElement {
|
|
|
154
191
|
this.#input.addEventListener("focus", (e) => {
|
|
155
192
|
this.#handleAutoComplete(e);
|
|
156
193
|
});
|
|
194
|
+
this.#input.addEventListener("show-results", (event) => {
|
|
195
|
+
this.dispatchEvent(
|
|
196
|
+
new CustomEvent("suggestions-updated", {
|
|
197
|
+
detail: { results: event?.detail?.results ?? [] },
|
|
198
|
+
bubbles: true,
|
|
199
|
+
composed: true,
|
|
200
|
+
})
|
|
201
|
+
);
|
|
202
|
+
});
|
|
157
203
|
}
|
|
158
204
|
|
|
159
205
|
async #adoptStyles() {
|
|
@@ -171,7 +217,9 @@ export class PdsOmnibox extends HTMLElement {
|
|
|
171
217
|
--ac-margin: var(--spacing-0);
|
|
172
218
|
--icon-size: var(--spacing-6);
|
|
173
219
|
--ac-itm-height-default: 5rem;
|
|
174
|
-
|
|
220
|
+
--ac-max-height-default: 300px;
|
|
221
|
+
--ac-viewport-gap: var(--spacing-4);
|
|
222
|
+
--ac-suggest-offset: var(--spacing-1);
|
|
175
223
|
}
|
|
176
224
|
|
|
177
225
|
.ac-container {
|
|
@@ -184,10 +232,14 @@ export class PdsOmnibox extends HTMLElement {
|
|
|
184
232
|
|
|
185
233
|
.ac-suggestion {
|
|
186
234
|
background-color: var(--color-surface-base);
|
|
187
|
-
max-height:
|
|
235
|
+
max-height: min(
|
|
236
|
+
var(--ac-max-height, var(--ac-max-height-default)),
|
|
237
|
+
calc(100dvh - var(--ac-viewport-gap))
|
|
238
|
+
);
|
|
188
239
|
position: absolute;
|
|
189
240
|
z-index: var(--z-dropdown);
|
|
190
241
|
left: 0;
|
|
242
|
+
top: calc(100% + var(--ac-suggest-offset));
|
|
191
243
|
padding: var(--ac-margin);
|
|
192
244
|
border-radius: 0 0 var(--ac-rad) var(--ac-rad);
|
|
193
245
|
box-shadow: var(--ac-box-shadow);
|
|
@@ -310,6 +362,11 @@ export class PdsOmnibox extends HTMLElement {
|
|
|
310
362
|
border-top-right-radius: 0;
|
|
311
363
|
}
|
|
312
364
|
|
|
365
|
+
.ac-suggestion {
|
|
366
|
+
top: auto;
|
|
367
|
+
bottom: calc(100% + var(--ac-suggest-offset));
|
|
368
|
+
}
|
|
369
|
+
|
|
313
370
|
.ac-itm:last-child {
|
|
314
371
|
border-bottom-left-radius: 0;
|
|
315
372
|
border-bottom-right-radius: 0;
|
|
@@ -359,6 +416,7 @@ export class PdsOmnibox extends HTMLElement {
|
|
|
359
416
|
|
|
360
417
|
this.#input.placeholder = this.placeholder;
|
|
361
418
|
this.#input.autocomplete = this.autocomplete;
|
|
419
|
+
if (this.#icon) this.#icon.setAttribute("icon", this.icon);
|
|
362
420
|
|
|
363
421
|
if (this.hasAttribute("value")) {
|
|
364
422
|
const v = this.getAttribute("value") || "";
|
|
@@ -416,13 +474,149 @@ export class PdsOmnibox extends HTMLElement {
|
|
|
416
474
|
// }
|
|
417
475
|
//AutoComplete.connect(ev, settings, this.#root);
|
|
418
476
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
477
|
+
this.#input._autoComplete = new AutoComplete(this.#input.parentNode, this.#input, settings);
|
|
478
|
+
this.#wrapAutoCompleteResultsHandler(this.#input._autoComplete);
|
|
479
|
+
setTimeout(() => {
|
|
480
|
+
this.#input._autoComplete.focusHandler(e);
|
|
481
|
+
this.#setupAutoCompleteSizing();
|
|
482
|
+
this.#updateSuggestionMaxHeight();
|
|
483
|
+
this.#setupSuggestionsObserver();
|
|
484
|
+
}, 100);
|
|
423
485
|
|
|
424
486
|
}
|
|
425
487
|
}
|
|
488
|
+
|
|
489
|
+
#wrapAutoCompleteResultsHandler(autoComplete) {
|
|
490
|
+
if (!autoComplete || autoComplete.__pdsSuggestionsWrapped) return;
|
|
491
|
+
autoComplete.__pdsSuggestionsWrapped = true;
|
|
492
|
+
const originalResultsHandler = autoComplete.resultsHandler?.bind(autoComplete);
|
|
493
|
+
if (!originalResultsHandler) return;
|
|
494
|
+
|
|
495
|
+
autoComplete.resultsHandler = (results, options) => {
|
|
496
|
+
this.dispatchEvent(
|
|
497
|
+
new CustomEvent("suggestions-updated", {
|
|
498
|
+
detail: { results },
|
|
499
|
+
bubbles: true,
|
|
500
|
+
composed: true,
|
|
501
|
+
})
|
|
502
|
+
);
|
|
503
|
+
return originalResultsHandler(results, options);
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
#setupAutoCompleteSizing() {
|
|
508
|
+
if (this.#autoCompleteResizeHandler) return;
|
|
509
|
+
this.#autoCompleteResizeHandler = () => this.#updateSuggestionMaxHeight();
|
|
510
|
+
this.#autoCompleteScrollHandler = () => this.#updateSuggestionMaxHeight();
|
|
511
|
+
this.#autoCompleteViewportHandler = () => this.#updateSuggestionMaxHeight();
|
|
512
|
+
|
|
513
|
+
window.addEventListener("resize", this.#autoCompleteResizeHandler);
|
|
514
|
+
window.addEventListener("scroll", this.#autoCompleteScrollHandler, true);
|
|
515
|
+
if (window.visualViewport) {
|
|
516
|
+
window.visualViewport.addEventListener("resize", this.#autoCompleteViewportHandler);
|
|
517
|
+
window.visualViewport.addEventListener("scroll", this.#autoCompleteViewportHandler);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
#setupSuggestionsObserver() {
|
|
522
|
+
if (this.#suggestionsObserver) return;
|
|
523
|
+
const container = this.#input?.parentElement;
|
|
524
|
+
const root = container?.shadowRoot ?? container;
|
|
525
|
+
const suggestion = root?.querySelector?.(".ac-suggestion");
|
|
526
|
+
if (!suggestion) return;
|
|
527
|
+
this.#suggestionsObserver = new MutationObserver(() => {
|
|
528
|
+
if (!suggestion.classList.contains("ac-active")) {
|
|
529
|
+
this.#resetIconToDefault();
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
this.#suggestionsObserver.observe(suggestion, {
|
|
533
|
+
attributes: true,
|
|
534
|
+
attributeFilter: ["class"],
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
#teardownSuggestionsObserver() {
|
|
539
|
+
if (!this.#suggestionsObserver) return;
|
|
540
|
+
this.#suggestionsObserver.disconnect();
|
|
541
|
+
this.#suggestionsObserver = null;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
#teardownAutoCompleteSizing() {
|
|
545
|
+
if (!this.#autoCompleteResizeHandler) return;
|
|
546
|
+
window.removeEventListener("resize", this.#autoCompleteResizeHandler);
|
|
547
|
+
window.removeEventListener("scroll", this.#autoCompleteScrollHandler, true);
|
|
548
|
+
if (window.visualViewport) {
|
|
549
|
+
window.visualViewport.removeEventListener("resize", this.#autoCompleteViewportHandler);
|
|
550
|
+
window.visualViewport.removeEventListener("scroll", this.#autoCompleteViewportHandler);
|
|
551
|
+
}
|
|
552
|
+
this.#autoCompleteResizeHandler = null;
|
|
553
|
+
this.#autoCompleteScrollHandler = null;
|
|
554
|
+
this.#autoCompleteViewportHandler = null;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
#updateSuggestionMaxHeight() {
|
|
558
|
+
if (!this.#input) return;
|
|
559
|
+
const container = this.#input.parentElement;
|
|
560
|
+
if (!container) return;
|
|
561
|
+
|
|
562
|
+
const rect = container.getBoundingClientRect();
|
|
563
|
+
const viewportHeight = window.visualViewport?.height || window.innerHeight;
|
|
564
|
+
const gap = this.#readSpacingToken(container, "--ac-viewport-gap") || 0;
|
|
565
|
+
const direction = container.getAttribute("data-direction") || "down";
|
|
566
|
+
|
|
567
|
+
const available = direction === "up"
|
|
568
|
+
? rect.top - gap
|
|
569
|
+
: viewportHeight - rect.bottom - gap;
|
|
570
|
+
|
|
571
|
+
const maxHeight = Math.max(0, Math.floor(available));
|
|
572
|
+
container.style.setProperty("--ac-max-height", `${maxHeight}px`);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
#readSpacingToken(element, tokenName) {
|
|
576
|
+
const value = getComputedStyle(element).getPropertyValue(tokenName).trim();
|
|
577
|
+
if (!value) return 0;
|
|
578
|
+
if (!this.#lengthProbe) return 0;
|
|
579
|
+
this.#lengthProbe.style.height = value;
|
|
580
|
+
const resolved = getComputedStyle(this.#lengthProbe).height;
|
|
581
|
+
const parsed = Number.parseFloat(resolved);
|
|
582
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
#handleSuggestionsUpdated(event) {
|
|
586
|
+
|
|
587
|
+
const results = event?.detail?.results;
|
|
588
|
+
if (!Array.isArray(results) || !this.settings?.categories) return;
|
|
589
|
+
if (!results.length) {
|
|
590
|
+
this.#icon?.setAttribute("icon", this.icon);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const categories = this.settings.categories;
|
|
595
|
+
const firstResult = results[0];
|
|
596
|
+
const categoryConfig = categories[firstResult?.category] || {};
|
|
597
|
+
const useIconForInput = categoryConfig?.useIconForInput ?? this.settings?.useIconForInput;
|
|
598
|
+
|
|
599
|
+
if (typeof useIconForInput === "string") {
|
|
600
|
+
this.#icon?.setAttribute("icon", useIconForInput);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (useIconForInput === true) {
|
|
605
|
+
const icon =
|
|
606
|
+
firstResult?.icon ||
|
|
607
|
+
firstResult?.element?.querySelector?.("pds-icon, svg-icon")?.getAttribute?.("icon");
|
|
608
|
+
if (icon) {
|
|
609
|
+
this.#icon?.setAttribute("icon", icon);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
this.#resetIconToDefault();
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
#resetIconToDefault() {
|
|
618
|
+
this.#icon?.setAttribute("icon", this.icon);
|
|
619
|
+
}
|
|
426
620
|
}
|
|
427
621
|
|
|
428
622
|
if (!customElements.get("pds-omnibox")) {
|