@ds-mo/ui 2.8.0 → 2.10.0
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/README.md +8 -9
- package/dist/.build-stamp +1 -1
- package/dist/components/ds-app-shell.js +1 -1
- package/dist/components/ds-badge.js +1 -1
- package/dist/components/ds-banner.js +1 -1
- package/dist/components/ds-bar-nav.js +1 -1
- package/dist/components/{ds-toggle-button.d.ts → ds-button-filled.d.ts} +4 -4
- package/dist/components/ds-button-filled.js +2 -0
- package/dist/components/ds-button-unfilled-icon.js +1 -1
- package/dist/components/{ds-button-group.d.ts → ds-card-setting.d.ts} +4 -4
- package/dist/components/ds-card-setting.js +2 -0
- package/dist/components/ds-card-setting.js.map +1 -0
- package/dist/components/ds-card.js +1 -1
- package/dist/components/ds-checkbox.js +1 -1
- package/dist/components/ds-chip.js +1 -1
- package/dist/components/ds-divider.js +1 -1
- package/dist/components/ds-empty-state.js +1 -1
- package/dist/components/ds-empty-state.js.map +1 -1
- package/dist/components/ds-fade.js +1 -1
- package/dist/components/ds-field.js +1 -1
- package/dist/components/ds-field.js.map +1 -1
- package/dist/components/ds-header.js +1 -1
- package/dist/components/ds-icon.js +1 -1
- package/dist/components/ds-input.js +1 -1
- package/dist/components/ds-loader.js +1 -1
- package/dist/components/ds-loader.js.map +1 -1
- package/dist/components/ds-menu.js +1 -1
- package/dist/components/ds-modal.js +1 -1
- package/dist/components/ds-panel-nav.js +1 -1
- package/dist/components/ds-panel-tools.js +1 -1
- package/dist/components/ds-panel-tools.js.map +1 -1
- package/dist/components/ds-radio-group.js +1 -1
- package/dist/components/ds-scrollbar.js +1 -1
- package/dist/components/ds-select.js +1 -1
- package/dist/components/ds-shell-gradient-picker.js +1 -1
- package/dist/components/ds-shell-gradient-swatch.js +1 -1
- package/dist/components/ds-slider.js +1 -1
- package/dist/components/ds-surface.js +1 -1
- package/dist/components/ds-tab-group-nav.js +1 -1
- package/dist/components/ds-tab-group.js +1 -1
- package/dist/components/ds-table.js +1 -1
- package/dist/components/ds-table.js.map +1 -1
- package/dist/components/ds-tag.js +1 -1
- package/dist/components/ds-text.js +1 -1
- package/dist/components/ds-toast-provider.js +1 -1
- package/dist/components/ds-toggle.js +1 -1
- package/dist/components/ds-tooltip.js +1 -1
- package/dist/components/ds-tooltip.js.map +1 -1
- package/dist/components/{p-g6MCxfm4.js → p-BFPfyJ8Z.js} +1 -1
- package/dist/components/p-BFPfyJ8Z.js.map +1 -0
- package/dist/components/{p-CiEc8Nwx.js → p-BOpHKDOG.js} +2 -2
- package/dist/components/p-BOpHKDOG.js.map +1 -0
- package/dist/components/{p-DvBrQuie.js → p-CDNVPM2x.js} +2 -2
- package/dist/components/{p-C9za7IDu.js → p-CJubWt1F.js} +2 -2
- package/dist/components/{p-DDEAcGac.js → p-CKCBqwIw.js} +2 -2
- package/dist/components/{p-DDEAcGac.js.map → p-CKCBqwIw.js.map} +1 -1
- package/dist/components/p-Cynjsv-P.js +2 -0
- package/dist/components/p-Cynjsv-P.js.map +1 -0
- package/dist/components/{p-B3ua1NkM.js → p-DnGr8qtq.js} +2 -2
- package/dist/components/p-Dul5eGGE.js +2 -0
- package/dist/components/p-Dul5eGGE.js.map +1 -0
- package/dist/components/{p-D4zKc1RW.js → p-evjnuLjj.js} +2 -2
- package/dist/components/p-jjfqrXnP.js +2 -0
- package/dist/components/p-jjfqrXnP.js.map +1 -0
- package/dist/components/p-w2Wrf0jC.js +2 -0
- package/dist/components/p-w2Wrf0jC.js.map +1 -0
- package/dist/types/components/Badge/Badge.d.ts +5 -1
- package/dist/types/components/ButtonFilled/ButtonFilled.d.ts +26 -0
- package/dist/types/components/ButtonFilled/index.d.ts +2 -0
- package/dist/types/components/ButtonUnfilledIcon/ButtonUnfilledIcon.d.ts +9 -2
- package/dist/types/components/ButtonUnfilledIcon/index.d.ts +1 -1
- package/dist/types/components/CardSetting/CardSetting.d.ts +16 -0
- package/dist/types/components/CardSetting/index.d.ts +2 -0
- package/dist/types/components/Icon/Icon.d.ts +14 -1
- package/dist/types/components/Icon/flag-icon-catalog.d.ts +2 -1
- package/dist/types/components/Icon/icon-cache.d.ts +25 -0
- package/dist/types/components/Icon/icon-svg.d.ts +26 -0
- package/dist/types/components/Icon/system-icon-catalog.d.ts +2 -1
- package/dist/types/components.d.ts +129 -414
- package/dist/types/utils/index.d.ts +1 -0
- package/package.json +2 -2
- package/src/angular/index.ts +2 -4
- package/src/angular/proxies.ts +40 -105
- package/src/react/components.ts +2 -4
- package/src/react/ds-button-filled.ts +27 -0
- package/src/react/ds-card-setting.ts +27 -0
- package/src/wc/components/Badge/Badge.tsx +6 -2
- package/src/wc/components/ButtonFilled/ButtonFilled.tsx +86 -0
- package/src/wc/components/ButtonFilled/index.ts +1 -0
- package/src/wc/components/ButtonUnfilledIcon/ButtonUnfilledIcon.tsx +19 -11
- package/src/wc/components/ButtonUnfilledIcon/index.ts +1 -1
- package/src/wc/components/CardSetting/CardSetting.tsx +96 -0
- package/src/wc/components/CardSetting/index.ts +1 -0
- package/src/wc/components/Icon/Icon.tsx +82 -22
- package/src/wc/components/Icon/flag-icon-catalog.ts +33 -65
- package/src/wc/components/Icon/icon-cache.ts +47 -0
- package/src/wc/components/Icon/icon-svg.ts +73 -0
- package/src/wc/components/Icon/system-icon-catalog.ts +398 -795
- package/src/wc/components.d.ts +129 -414
- package/src/wc/nav/badge-gradient-ring.ts +4 -1
- package/src/wc/utils/index.ts +1 -0
- package/dist/components/ds-button-group.js +0 -2
- package/dist/components/ds-button-group.js.map +0 -1
- package/dist/components/ds-button.d.ts +0 -11
- package/dist/components/ds-button.js +0 -2
- package/dist/components/ds-toggle-button-group.d.ts +0 -11
- package/dist/components/ds-toggle-button-group.js +0 -2
- package/dist/components/ds-toggle-button-group.js.map +0 -1
- package/dist/components/ds-toggle-button.js +0 -2
- package/dist/components/ds-toggle-button.js.map +0 -1
- package/dist/components/p-BPMJx63I.js +0 -2
- package/dist/components/p-BPMJx63I.js.map +0 -1
- package/dist/components/p-BTndnbR_.js +0 -2
- package/dist/components/p-BTndnbR_.js.map +0 -1
- package/dist/components/p-C0CUGiLm.js +0 -2
- package/dist/components/p-C0CUGiLm.js.map +0 -1
- package/dist/components/p-CiEc8Nwx.js.map +0 -1
- package/dist/components/p-DUDppafq.js +0 -2
- package/dist/components/p-DUDppafq.js.map +0 -1
- package/dist/components/p-DgDSRPSP.js +0 -2
- package/dist/components/p-DgDSRPSP.js.map +0 -1
- package/dist/components/p-DqBCQRRd.js +0 -2
- package/dist/components/p-DqBCQRRd.js.map +0 -1
- package/dist/components/p-g6MCxfm4.js.map +0 -1
- package/dist/types/components/Button/Button.d.ts +0 -38
- package/dist/types/components/Button/index.d.ts +0 -2
- package/dist/types/components/ButtonGroup/ButtonGroup.d.ts +0 -29
- package/dist/types/components/ToggleButton/ToggleButton.d.ts +0 -37
- package/dist/types/components/ToggleButtonGroup/ToggleButtonGroup.d.ts +0 -33
- package/src/react/ds-button-group.ts +0 -27
- package/src/react/ds-button.ts +0 -35
- package/src/react/ds-toggle-button-group.ts +0 -27
- package/src/react/ds-toggle-button.ts +0 -27
- package/src/wc/components/Button/Button.tsx +0 -158
- package/src/wc/components/Button/index.ts +0 -1
- package/src/wc/components/ButtonGroup/ButtonGroup.tsx +0 -110
- package/src/wc/components/ToggleButton/ToggleButton.tsx +0 -124
- package/src/wc/components/ToggleButtonGroup/ToggleButtonGroup.tsx +0 -118
- /package/dist/components/{ds-button.js.map → ds-button-filled.js.map} +0 -0
- /package/dist/components/{p-DvBrQuie.js.map → p-CDNVPM2x.js.map} +0 -0
- /package/dist/components/{p-C9za7IDu.js.map → p-CJubWt1F.js.map} +0 -0
- /package/dist/components/{p-B3ua1NkM.js.map → p-DnGr8qtq.js.map} +0 -0
- /package/dist/components/{p-D4zKc1RW.js.map → p-evjnuLjj.js.map} +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { Component, Prop, Element, Watch, h, Host } from '@stencil/core';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { Component, Prop, Element, State, Watch, h, Host } from '@stencil/core';
|
|
2
|
+
import { flagIconLoaders } from './flag-icon-catalog';
|
|
3
|
+
import { systemIconLoaders } from './system-icon-catalog';
|
|
4
|
+
import { iconCache, iconCacheKey } from './icon-cache';
|
|
5
|
+
import { parseIconSvg } from './icon-svg';
|
|
4
6
|
|
|
5
7
|
export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl';
|
|
6
8
|
|
|
@@ -47,36 +49,94 @@ export class Icon {
|
|
|
47
49
|
/** Set `true` to look up from the flag icon set instead of the system icon set. */
|
|
48
50
|
@Prop() flag: boolean = false;
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
/** Resolved SVG markup — set synchronously on cache hit, async after a lazy load. */
|
|
53
|
+
@State() private svg: string = '';
|
|
54
|
+
|
|
55
|
+
/** Guards against out-of-order async resolutions when `name`/`flag` change quickly. */
|
|
56
|
+
private loadToken = 0;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Cache hit → render synchronously (pre-registered via `registerIcons` or
|
|
60
|
+
* previously loaded). Miss → fire the per-icon lazy loader; the glyph pops in
|
|
61
|
+
* when the (tiny, per-icon) chunk resolves and stays cached for every other
|
|
62
|
+
* ds-icon instance.
|
|
63
|
+
*/
|
|
64
|
+
private resolveSvg() {
|
|
65
|
+
const flag = this.flag;
|
|
66
|
+
const key = iconCacheKey(this.name, flag);
|
|
67
|
+
const token = ++this.loadToken;
|
|
68
|
+
|
|
69
|
+
const cached = iconCache().get(key);
|
|
70
|
+
if (cached !== undefined) {
|
|
71
|
+
this.svg = cached;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Own-key lookup only: names resolve by exact canonical IcoMo export key —
|
|
76
|
+
// never meta.json aliases (not in the maps) and never inherited prototype
|
|
77
|
+
// keys ('constructor', 'toString', …) that a bare index access would hit.
|
|
78
|
+
const loaders = flag ? flagIconLoaders : systemIconLoaders;
|
|
79
|
+
const loader = Object.prototype.hasOwnProperty.call(loaders, this.name)
|
|
80
|
+
? loaders[this.name]
|
|
81
|
+
: undefined;
|
|
82
|
+
if (!loader) {
|
|
83
|
+
this.svg = '';
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
loader()
|
|
88
|
+
.then(svg => {
|
|
89
|
+
iconCache().set(key, svg);
|
|
90
|
+
if (token === this.loadToken) this.svg = svg;
|
|
91
|
+
})
|
|
92
|
+
.catch(() => {
|
|
93
|
+
if (token === this.loadToken) this.svg = '';
|
|
94
|
+
});
|
|
53
95
|
}
|
|
54
96
|
|
|
97
|
+
componentWillLoad() {
|
|
98
|
+
// Fire-and-forget: do not return the promise — a cache miss must not block
|
|
99
|
+
// the parent render tree on a network fetch.
|
|
100
|
+
this.resolveSvg();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Last markup injected into the DOM — skips redundant re-parse on unrelated re-renders. */
|
|
104
|
+
private renderedSvg: string | null = null;
|
|
105
|
+
|
|
55
106
|
private updateSvg() {
|
|
56
107
|
const container = this.el.querySelector<HTMLElement>('.icon__svg');
|
|
57
108
|
if (!container) return;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
svg.setAttribute('aria-hidden', 'true');
|
|
69
|
-
svg.style.display = 'block';
|
|
70
|
-
svg.style.flexShrink = '0';
|
|
71
|
-
svg.style.width = '100%';
|
|
72
|
-
svg.style.height = '100%';
|
|
109
|
+
if (this.svg === this.renderedSvg) return;
|
|
110
|
+
this.renderedSvg = this.svg;
|
|
111
|
+
|
|
112
|
+
// Validate + inject as parsed DOM nodes — never innerHTML. Keeps ds-icon
|
|
113
|
+
// Trusted-Types compatible and rejects executable/foreign content in
|
|
114
|
+
// glyph strings (registerIcons accepts app-provided markup).
|
|
115
|
+
const svg = this.svg ? parseIconSvg(this.svg) : null;
|
|
116
|
+
if (!svg) {
|
|
117
|
+
container.replaceChildren();
|
|
118
|
+
return;
|
|
73
119
|
}
|
|
120
|
+
|
|
121
|
+
// Stencil's scoped CSS cannot reach injected elements (no sc-* class).
|
|
122
|
+
// Apply width/height as inline styles so sizing works regardless of scope class.
|
|
123
|
+
svg.removeAttribute('width');
|
|
124
|
+
svg.removeAttribute('height');
|
|
125
|
+
// Prevent SVG from being natively focusable (Firefox/IE default) while
|
|
126
|
+
// sitting inside an aria-hidden parent — fixes aria-hidden-focus violation.
|
|
127
|
+
svg.setAttribute('focusable', 'false');
|
|
128
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
129
|
+
svg.style.display = 'block';
|
|
130
|
+
svg.style.flexShrink = '0';
|
|
131
|
+
svg.style.width = '100%';
|
|
132
|
+
svg.style.height = '100%';
|
|
133
|
+
container.replaceChildren(svg);
|
|
74
134
|
}
|
|
75
135
|
|
|
76
136
|
@Watch('name')
|
|
77
137
|
@Watch('flag')
|
|
78
138
|
onIconChange() {
|
|
79
|
-
this.
|
|
139
|
+
this.resolveSvg();
|
|
80
140
|
}
|
|
81
141
|
|
|
82
142
|
componentDidRender() {
|
|
@@ -1,68 +1,36 @@
|
|
|
1
1
|
/** Auto-generated by scripts/generate-icon-catalog.mjs — do not edit. */
|
|
2
|
-
import { FlagAustria } from '@ds-mo/icons/svg/flags/FlagAustria';
|
|
3
|
-
import { FlagBelgium } from '@ds-mo/icons/svg/flags/FlagBelgium';
|
|
4
|
-
import { FlagBulgaria } from '@ds-mo/icons/svg/flags/FlagBulgaria';
|
|
5
|
-
import { FlagCanada } from '@ds-mo/icons/svg/flags/FlagCanada';
|
|
6
|
-
import { FlagCroatia } from '@ds-mo/icons/svg/flags/FlagCroatia';
|
|
7
|
-
import { FlagCyprus } from '@ds-mo/icons/svg/flags/FlagCyprus';
|
|
8
|
-
import { FlagCzechRepublic } from '@ds-mo/icons/svg/flags/FlagCzechRepublic';
|
|
9
|
-
import { FlagDenmark } from '@ds-mo/icons/svg/flags/FlagDenmark';
|
|
10
|
-
import { FlagEU } from '@ds-mo/icons/svg/flags/FlagEU';
|
|
11
|
-
import { FlagEstonia } from '@ds-mo/icons/svg/flags/FlagEstonia';
|
|
12
|
-
import { FlagFinland } from '@ds-mo/icons/svg/flags/FlagFinland';
|
|
13
|
-
import { FlagFrance } from '@ds-mo/icons/svg/flags/FlagFrance';
|
|
14
|
-
import { FlagGermany } from '@ds-mo/icons/svg/flags/FlagGermany';
|
|
15
|
-
import { FlagGreece } from '@ds-mo/icons/svg/flags/FlagGreece';
|
|
16
|
-
import { FlagHungary } from '@ds-mo/icons/svg/flags/FlagHungary';
|
|
17
|
-
import { FlagIreland } from '@ds-mo/icons/svg/flags/FlagIreland';
|
|
18
|
-
import { FlagItaly } from '@ds-mo/icons/svg/flags/FlagItaly';
|
|
19
|
-
import { FlagLatvia } from '@ds-mo/icons/svg/flags/FlagLatvia';
|
|
20
|
-
import { FlagLithuania } from '@ds-mo/icons/svg/flags/FlagLithuania';
|
|
21
|
-
import { FlagLuxembourg } from '@ds-mo/icons/svg/flags/FlagLuxembourg';
|
|
22
|
-
import { FlagMalta } from '@ds-mo/icons/svg/flags/FlagMalta';
|
|
23
|
-
import { FlagMexico } from '@ds-mo/icons/svg/flags/FlagMexico';
|
|
24
|
-
import { FlagNetherlands } from '@ds-mo/icons/svg/flags/FlagNetherlands';
|
|
25
|
-
import { FlagPoland } from '@ds-mo/icons/svg/flags/FlagPoland';
|
|
26
|
-
import { FlagPortugal } from '@ds-mo/icons/svg/flags/FlagPortugal';
|
|
27
|
-
import { FlagRomania } from '@ds-mo/icons/svg/flags/FlagRomania';
|
|
28
|
-
import { FlagSlovakia } from '@ds-mo/icons/svg/flags/FlagSlovakia';
|
|
29
|
-
import { FlagSlovenia } from '@ds-mo/icons/svg/flags/FlagSlovenia';
|
|
30
|
-
import { FlagSpain } from '@ds-mo/icons/svg/flags/FlagSpain';
|
|
31
|
-
import { FlagSweden } from '@ds-mo/icons/svg/flags/FlagSweden';
|
|
32
|
-
import { FlagUnitedKingdom } from '@ds-mo/icons/svg/flags/FlagUnitedKingdom';
|
|
33
|
-
import { FlagUnitedStates } from '@ds-mo/icons/svg/flags/FlagUnitedStates';
|
|
34
2
|
|
|
35
|
-
export const
|
|
36
|
-
FlagAustria,
|
|
37
|
-
FlagBelgium,
|
|
38
|
-
FlagBulgaria,
|
|
39
|
-
FlagCanada,
|
|
40
|
-
FlagCroatia,
|
|
41
|
-
FlagCyprus,
|
|
42
|
-
FlagCzechRepublic,
|
|
43
|
-
FlagDenmark,
|
|
44
|
-
FlagEU,
|
|
45
|
-
FlagEstonia,
|
|
46
|
-
FlagFinland,
|
|
47
|
-
FlagFrance,
|
|
48
|
-
FlagGermany,
|
|
49
|
-
FlagGreece,
|
|
50
|
-
FlagHungary,
|
|
51
|
-
FlagIreland,
|
|
52
|
-
FlagItaly,
|
|
53
|
-
FlagLatvia,
|
|
54
|
-
FlagLithuania,
|
|
55
|
-
FlagLuxembourg,
|
|
56
|
-
FlagMalta,
|
|
57
|
-
FlagMexico,
|
|
58
|
-
FlagNetherlands,
|
|
59
|
-
FlagPoland,
|
|
60
|
-
FlagPortugal,
|
|
61
|
-
FlagRomania,
|
|
62
|
-
FlagSlovakia,
|
|
63
|
-
FlagSlovenia,
|
|
64
|
-
FlagSpain,
|
|
65
|
-
FlagSweden,
|
|
66
|
-
FlagUnitedKingdom,
|
|
67
|
-
FlagUnitedStates,
|
|
3
|
+
export const flagIconLoaders: Record<string, () => Promise<string>> = {
|
|
4
|
+
FlagAustria: () => import('@ds-mo/icons/svg/flags/FlagAustria').then(m => m.FlagAustria),
|
|
5
|
+
FlagBelgium: () => import('@ds-mo/icons/svg/flags/FlagBelgium').then(m => m.FlagBelgium),
|
|
6
|
+
FlagBulgaria: () => import('@ds-mo/icons/svg/flags/FlagBulgaria').then(m => m.FlagBulgaria),
|
|
7
|
+
FlagCanada: () => import('@ds-mo/icons/svg/flags/FlagCanada').then(m => m.FlagCanada),
|
|
8
|
+
FlagCroatia: () => import('@ds-mo/icons/svg/flags/FlagCroatia').then(m => m.FlagCroatia),
|
|
9
|
+
FlagCyprus: () => import('@ds-mo/icons/svg/flags/FlagCyprus').then(m => m.FlagCyprus),
|
|
10
|
+
FlagCzechRepublic: () => import('@ds-mo/icons/svg/flags/FlagCzechRepublic').then(m => m.FlagCzechRepublic),
|
|
11
|
+
FlagDenmark: () => import('@ds-mo/icons/svg/flags/FlagDenmark').then(m => m.FlagDenmark),
|
|
12
|
+
FlagEU: () => import('@ds-mo/icons/svg/flags/FlagEU').then(m => m.FlagEU),
|
|
13
|
+
FlagEstonia: () => import('@ds-mo/icons/svg/flags/FlagEstonia').then(m => m.FlagEstonia),
|
|
14
|
+
FlagFinland: () => import('@ds-mo/icons/svg/flags/FlagFinland').then(m => m.FlagFinland),
|
|
15
|
+
FlagFrance: () => import('@ds-mo/icons/svg/flags/FlagFrance').then(m => m.FlagFrance),
|
|
16
|
+
FlagGermany: () => import('@ds-mo/icons/svg/flags/FlagGermany').then(m => m.FlagGermany),
|
|
17
|
+
FlagGreece: () => import('@ds-mo/icons/svg/flags/FlagGreece').then(m => m.FlagGreece),
|
|
18
|
+
FlagHungary: () => import('@ds-mo/icons/svg/flags/FlagHungary').then(m => m.FlagHungary),
|
|
19
|
+
FlagIreland: () => import('@ds-mo/icons/svg/flags/FlagIreland').then(m => m.FlagIreland),
|
|
20
|
+
FlagItaly: () => import('@ds-mo/icons/svg/flags/FlagItaly').then(m => m.FlagItaly),
|
|
21
|
+
FlagLatvia: () => import('@ds-mo/icons/svg/flags/FlagLatvia').then(m => m.FlagLatvia),
|
|
22
|
+
FlagLithuania: () => import('@ds-mo/icons/svg/flags/FlagLithuania').then(m => m.FlagLithuania),
|
|
23
|
+
FlagLuxembourg: () => import('@ds-mo/icons/svg/flags/FlagLuxembourg').then(m => m.FlagLuxembourg),
|
|
24
|
+
FlagMalta: () => import('@ds-mo/icons/svg/flags/FlagMalta').then(m => m.FlagMalta),
|
|
25
|
+
FlagMexico: () => import('@ds-mo/icons/svg/flags/FlagMexico').then(m => m.FlagMexico),
|
|
26
|
+
FlagNetherlands: () => import('@ds-mo/icons/svg/flags/FlagNetherlands').then(m => m.FlagNetherlands),
|
|
27
|
+
FlagPoland: () => import('@ds-mo/icons/svg/flags/FlagPoland').then(m => m.FlagPoland),
|
|
28
|
+
FlagPortugal: () => import('@ds-mo/icons/svg/flags/FlagPortugal').then(m => m.FlagPortugal),
|
|
29
|
+
FlagRomania: () => import('@ds-mo/icons/svg/flags/FlagRomania').then(m => m.FlagRomania),
|
|
30
|
+
FlagSlovakia: () => import('@ds-mo/icons/svg/flags/FlagSlovakia').then(m => m.FlagSlovakia),
|
|
31
|
+
FlagSlovenia: () => import('@ds-mo/icons/svg/flags/FlagSlovenia').then(m => m.FlagSlovenia),
|
|
32
|
+
FlagSpain: () => import('@ds-mo/icons/svg/flags/FlagSpain').then(m => m.FlagSpain),
|
|
33
|
+
FlagSweden: () => import('@ds-mo/icons/svg/flags/FlagSweden').then(m => m.FlagSweden),
|
|
34
|
+
FlagUnitedKingdom: () => import('@ds-mo/icons/svg/flags/FlagUnitedKingdom').then(m => m.FlagUnitedKingdom),
|
|
35
|
+
FlagUnitedStates: () => import('@ds-mo/icons/svg/flags/FlagUnitedStates').then(m => m.FlagUnitedStates),
|
|
68
36
|
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared SVG glyph cache for ds-icon.
|
|
3
|
+
*
|
|
4
|
+
* Keyed on a global symbol so every module instance (the compiled Stencil chunk
|
|
5
|
+
* inside dist/components and any direct source import, e.g. via `@ds-mo/ui/utils`)
|
|
6
|
+
* reads and writes the same cache. Without this, `registerIcons` called from an
|
|
7
|
+
* app would fill a different Map than the one ds-icon resolves from.
|
|
8
|
+
*/
|
|
9
|
+
const CACHE_KEY = Symbol.for('ds-mo.icon-svg-cache');
|
|
10
|
+
|
|
11
|
+
type IconCacheMap = Map<string, string>;
|
|
12
|
+
|
|
13
|
+
type GlobalWithIconCache = { [CACHE_KEY]?: IconCacheMap };
|
|
14
|
+
|
|
15
|
+
export function iconCacheKey(name: string, flag: boolean): string {
|
|
16
|
+
return flag ? `flag:${name}` : `system:${name}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function iconCache(): IconCacheMap {
|
|
20
|
+
const g = globalThis as GlobalWithIconCache;
|
|
21
|
+
return (g[CACHE_KEY] ??= new Map());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Pre-register glyphs so ds-icon renders them synchronously — no lazy-chunk
|
|
26
|
+
* fetch, no first-paint pop-in. Import the SVG strings from `@ds-mo/icons`
|
|
27
|
+
* in the app (statically, so they ship in the app's own bundle):
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { Bell } from '@ds-mo/icons/svg/Bell';
|
|
31
|
+
* import { US } from '@ds-mo/icons/svg/flags/US';
|
|
32
|
+
* import { registerIcons } from '@ds-mo/ui/utils';
|
|
33
|
+
*
|
|
34
|
+
* registerIcons({ Bell });
|
|
35
|
+
* registerIcons({ US }, { flag: true });
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* Icons that are not registered still work — they lazy-load on first render
|
|
39
|
+
* and stay cached afterwards.
|
|
40
|
+
*/
|
|
41
|
+
export function registerIcons(icons: Record<string, string>, options?: { flag?: boolean }): void {
|
|
42
|
+
const cache = iconCache();
|
|
43
|
+
const flag = options?.flag === true;
|
|
44
|
+
for (const [name, svg] of Object.entries(icons)) {
|
|
45
|
+
cache.set(iconCacheKey(name, flag), svg);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Glyph markup validation + Trusted-Types-safe parsing for ds-icon.
|
|
3
|
+
*
|
|
4
|
+
* Glyphs normally come from the trusted @ds-mo/icons build pipeline, but
|
|
5
|
+
* `registerIcons` accepts arbitrary app-provided strings and injection is a
|
|
6
|
+
* sink — so every glyph is validated at the render boundary. Injection uses
|
|
7
|
+
* parsed DOM nodes (never innerHTML), which keeps ds-icon working under a
|
|
8
|
+
* strict CSP with `require-trusted-types-for 'script'`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Structural view of an element so the walk is unit-testable without a DOM. */
|
|
12
|
+
export interface IconSvgElementLike {
|
|
13
|
+
localName: string;
|
|
14
|
+
getAttributeNames(): string[];
|
|
15
|
+
getAttribute(name: string): string | null;
|
|
16
|
+
children: ArrayLike<IconSvgElementLike>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Elements that can execute or embed foreign content — never valid in a glyph. */
|
|
20
|
+
const FORBIDDEN_ELEMENTS = new Set([
|
|
21
|
+
'script',
|
|
22
|
+
'foreignobject',
|
|
23
|
+
'iframe',
|
|
24
|
+
'object',
|
|
25
|
+
'embed',
|
|
26
|
+
'animation', // SVG 1.2 external-content element (distinct from animate/animateTransform)
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
function isValidIconSvgElement(el: IconSvgElementLike): boolean {
|
|
30
|
+
if (FORBIDDEN_ELEMENTS.has(el.localName.toLowerCase())) return false;
|
|
31
|
+
|
|
32
|
+
for (const attr of el.getAttributeNames()) {
|
|
33
|
+
const lower = attr.toLowerCase();
|
|
34
|
+
// Event handler attributes (onclick, onload, …) — any casing.
|
|
35
|
+
if (lower.startsWith('on')) return false;
|
|
36
|
+
// References must stay internal (#gradient-id); rejects javascript:, data:,
|
|
37
|
+
// and external URLs on <use>, <a>, <image>, …
|
|
38
|
+
if (lower === 'href' || lower === 'xlink:href') {
|
|
39
|
+
const value = el.getAttribute(attr) ?? '';
|
|
40
|
+
if (!value.startsWith('#')) return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < el.children.length; i++) {
|
|
45
|
+
if (!isValidIconSvgElement(el.children[i])) return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** True when `root` is an `<svg>` whose whole tree passes validation. */
|
|
52
|
+
export function isValidIconSvgRoot(root: IconSvgElementLike | null): boolean {
|
|
53
|
+
return root != null && root.localName === 'svg' && isValidIconSvgElement(root);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Parse + validate glyph markup. Returns a detached `<svg>` element ready to
|
|
58
|
+
* append (appending adopts it into the document), or `null` when the markup
|
|
59
|
+
* is not well-formed XML, not rooted at `<svg>`, or fails validation —
|
|
60
|
+
* ds-icon renders an empty fixed-size box in that case.
|
|
61
|
+
*/
|
|
62
|
+
export function parseIconSvg(svg: string): SVGElement | null {
|
|
63
|
+
if (typeof DOMParser === 'undefined') return null;
|
|
64
|
+
|
|
65
|
+
const doc = new DOMParser().parseFromString(svg, 'image/svg+xml');
|
|
66
|
+
// Malformed XML yields a parsererror document (root or nested, per browser).
|
|
67
|
+
if (doc.querySelector('parsererror')) return null;
|
|
68
|
+
|
|
69
|
+
const root = doc.documentElement as unknown as IconSvgElementLike;
|
|
70
|
+
if (!isValidIconSvgRoot(root)) return null;
|
|
71
|
+
|
|
72
|
+
return doc.documentElement as unknown as SVGElement;
|
|
73
|
+
}
|