@incursa/ui-kit 1.7.0 → 1.8.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.
Files changed (45) hide show
  1. package/NOTICE +8 -0
  2. package/README.md +16 -0
  3. package/dist/icons/index.js +371 -0
  4. package/dist/icons/package.json +3 -0
  5. package/dist/inc-design-language.css +34 -1
  6. package/dist/inc-design-language.css.map +1 -1
  7. package/dist/inc-design-language.js +1558 -1462
  8. package/dist/inc-design-language.min.css +1 -1
  9. package/dist/inc-design-language.min.css.map +1 -1
  10. package/dist/mcp/components/cards.json +3 -3
  11. package/dist/mcp/components/metrics.json +3 -3
  12. package/dist/mcp/components/states.json +3 -3
  13. package/dist/mcp/examples/data-grid-advanced.json +2 -2
  14. package/dist/mcp/examples/demo.json +2 -2
  15. package/dist/mcp/examples/overlay-workflows.json +2 -2
  16. package/dist/mcp/examples/reference.json +2 -2
  17. package/dist/mcp/examples/states.json +2 -2
  18. package/dist/mcp/examples/web-components.json +2 -2
  19. package/dist/mcp/guides/latest.json +2 -2
  20. package/dist/mcp/guides/package-metadata.json +2 -2
  21. package/dist/mcp/guides/update.json +2 -2
  22. package/dist/mcp/install.json +1 -1
  23. package/dist/mcp/patterns/data-grid-advanced.json +2 -2
  24. package/dist/mcp/patterns/demo.json +2 -2
  25. package/dist/mcp/patterns/overlay-workflows.json +2 -2
  26. package/dist/mcp/patterns/reference.json +2 -2
  27. package/dist/mcp/patterns/states.json +2 -2
  28. package/dist/mcp/patterns/web-components.json +2 -2
  29. package/dist/mcp/resources.json +77 -74
  30. package/dist/mcp/search-index.json +21 -21
  31. package/dist/mcp/update.json +2 -2
  32. package/dist/mcp/worker.mjs +164 -281
  33. package/dist/mcp/worker.mjs.map +2 -2
  34. package/dist/web-components/README.md +4 -0
  35. package/dist/web-components/components/actions.js +149 -2
  36. package/dist/web-components/components/feedback.js +63 -12
  37. package/dist/web-components/index.js +501 -14
  38. package/package.json +10 -3
  39. package/src/icons/index.js +229 -0
  40. package/src/icons/package.json +3 -0
  41. package/src/inc-design-language.js +12 -11
  42. package/src/inc-design-language.scss +38 -1
  43. package/src/web-components/README.md +4 -0
  44. package/src/web-components/components/actions.js +149 -2
  45. package/src/web-components/components/feedback.js +63 -12
@@ -0,0 +1,229 @@
1
+ import createLucideElement from "lucide/dist/esm/createElement.mjs";
2
+ import CircleCheck from "lucide/dist/esm/icons/circle-check.mjs";
3
+ import CircleHelp from "lucide/dist/esm/icons/circle-question-mark.mjs";
4
+ import CircleX from "lucide/dist/esm/icons/circle-x.mjs";
5
+ import Download from "lucide/dist/esm/icons/download.mjs";
6
+ import ExternalLink from "lucide/dist/esm/icons/external-link.mjs";
7
+ import FileText from "lucide/dist/esm/icons/file-text.mjs";
8
+ import FolderPlus from "lucide/dist/esm/icons/folder-plus.mjs";
9
+ import Info from "lucide/dist/esm/icons/info.mjs";
10
+ import Lock from "lucide/dist/esm/icons/lock.mjs";
11
+ import Pause from "lucide/dist/esm/icons/pause.mjs";
12
+ import Play from "lucide/dist/esm/icons/play.mjs";
13
+ import RefreshCw from "lucide/dist/esm/icons/refresh-cw.mjs";
14
+ import SearchX from "lucide/dist/esm/icons/search-x.mjs";
15
+ import Settings from "lucide/dist/esm/icons/settings.mjs";
16
+ import ShieldCheck from "lucide/dist/esm/icons/shield-check.mjs";
17
+ import TriangleAlert from "lucide/dist/esm/icons/triangle-alert.mjs";
18
+ import Upload from "lucide/dist/esm/icons/upload.mjs";
19
+
20
+ const ICON_NODES = Object.freeze({
21
+ info: Info,
22
+ help: CircleHelp,
23
+ success: CircleCheck,
24
+ warning: TriangleAlert,
25
+ error: CircleX,
26
+ upload: Upload,
27
+ document: FileText,
28
+ download: Download,
29
+ settings: Settings,
30
+ "external-link": ExternalLink,
31
+ empty: FolderPlus,
32
+ "no-results": SearchX,
33
+ loading: RefreshCw,
34
+ lock: Lock,
35
+ pause: Pause,
36
+ play: Play,
37
+ permission: ShieldCheck,
38
+ });
39
+
40
+ const ICON_NAMES = Object.freeze(Object.keys(ICON_NODES));
41
+ const DEFAULT_SIZE = 16;
42
+
43
+ function getNamespace() {
44
+ if (typeof globalThis === "undefined") {
45
+ return null;
46
+ }
47
+
48
+ const root = globalThis.IncWebComponents || (globalThis.IncWebComponents = {});
49
+ const icons = root.icons || (root.icons = {});
50
+
51
+ if (!icons.names) {
52
+ icons.names = ICON_NAMES;
53
+ }
54
+
55
+ if (!icons.defaultRenderer) {
56
+ icons.defaultRenderer = renderDefaultIcon;
57
+ }
58
+
59
+ if (!icons.render) {
60
+ icons.render = renderIncIcon;
61
+ }
62
+
63
+ if (!icons.setRenderer) {
64
+ icons.setRenderer = setIconRenderer;
65
+ }
66
+
67
+ return icons;
68
+ }
69
+
70
+ function normalizeIconName(name) {
71
+ return String(name || "")
72
+ .trim()
73
+ .toLowerCase()
74
+ .replace(/[_\s]+/g, "-");
75
+ }
76
+
77
+ function normalizeSize(value) {
78
+ const parsed = Number.parseFloat(value);
79
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_SIZE;
80
+ }
81
+
82
+ function buildIconAttributes(name, options = {}) {
83
+ const size = normalizeSize(options.size);
84
+ const className = options.className || "inc-icon";
85
+ const attrs = {
86
+ width: size,
87
+ height: size,
88
+ "data-inc-icon": name,
89
+ class: className,
90
+ focusable: "false",
91
+ };
92
+
93
+ if (options.decorative !== false) {
94
+ attrs["aria-hidden"] = "true";
95
+ } else {
96
+ attrs.role = "img";
97
+ attrs["aria-label"] = options.label || name;
98
+ }
99
+
100
+ return attrs;
101
+ }
102
+
103
+ function renderDefaultIcon(name, options = {}) {
104
+ const normalizedName = normalizeIconName(name);
105
+ const iconNode = ICON_NODES[normalizedName] || ICON_NODES.info;
106
+
107
+ if (typeof document === "undefined") {
108
+ return "";
109
+ }
110
+
111
+ return createLucideElement(iconNode, buildIconAttributes(normalizedName, options));
112
+ }
113
+
114
+ function coerceIconResult(result) {
115
+ if (!result || typeof document === "undefined") {
116
+ return null;
117
+ }
118
+
119
+ if (result instanceof Node) {
120
+ return result;
121
+ }
122
+
123
+ if (typeof result === "string") {
124
+ const template = document.createElement("template");
125
+ template.innerHTML = result.trim();
126
+ return template.content.firstElementChild || null;
127
+ }
128
+
129
+ return null;
130
+ }
131
+
132
+ function getIconRenderer() {
133
+ const namespace = getNamespace();
134
+ return typeof namespace?.renderer === "function"
135
+ ? namespace.renderer
136
+ : renderDefaultIcon;
137
+ }
138
+
139
+ function setIconRenderer(renderer) {
140
+ const namespace = getNamespace();
141
+ if (!namespace) {
142
+ return null;
143
+ }
144
+
145
+ if (renderer == null) {
146
+ delete namespace.renderer;
147
+ return null;
148
+ }
149
+
150
+ if (typeof renderer !== "function") {
151
+ throw new TypeError("Inc icon renderer must be a function.");
152
+ }
153
+
154
+ namespace.renderer = renderer;
155
+ return renderer;
156
+ }
157
+
158
+ function renderIncIcon(name, options = {}) {
159
+ const normalizedName = normalizeIconName(name) || "info";
160
+ const renderer = getIconRenderer();
161
+ const rendered = renderer(normalizedName, options);
162
+ const icon = coerceIconResult(rendered) || coerceIconResult(renderDefaultIcon(normalizedName, options));
163
+
164
+ if (icon instanceof Element && options.decorative !== false) {
165
+ icon.setAttribute("aria-hidden", "true");
166
+ icon.removeAttribute("aria-label");
167
+ icon.removeAttribute("role");
168
+ }
169
+
170
+ return icon;
171
+ }
172
+
173
+ function replaceIconContents(container, name, options = {}) {
174
+ if (!(container instanceof Element)) {
175
+ return null;
176
+ }
177
+
178
+ container.replaceChildren();
179
+ const icon = renderIncIcon(name, options);
180
+ if (icon) {
181
+ icon.setAttribute("data-inc-generated-icon", "true");
182
+ icon.setAttribute("data-inc-icon-upgraded", "true");
183
+ container.append(icon);
184
+ }
185
+
186
+ return icon;
187
+ }
188
+
189
+ function upgradeIconPlaceholders(root = typeof document !== "undefined" ? document : null) {
190
+ if (!root || typeof root.querySelectorAll !== "function") {
191
+ return [];
192
+ }
193
+
194
+ const upgraded = [];
195
+ root.querySelectorAll("[data-inc-icon]").forEach((node) => {
196
+ if (!(node instanceof Element)) {
197
+ return;
198
+ }
199
+
200
+ const name = node.getAttribute("data-inc-icon");
201
+ if (!name || node.hasAttribute("data-inc-icon-upgraded") || node.hasAttribute("data-inc-generated-icon")) {
202
+ return;
203
+ }
204
+
205
+ replaceIconContents(node, name, {
206
+ className: node.getAttribute("data-inc-icon-class") || "inc-icon",
207
+ decorative: node.getAttribute("aria-hidden") !== "false",
208
+ label: node.getAttribute("aria-label") || undefined,
209
+ size: node.getAttribute("data-inc-icon-size") || undefined,
210
+ });
211
+ node.setAttribute("data-inc-icon-upgraded", "true");
212
+ upgraded.push(node);
213
+ });
214
+
215
+ return upgraded;
216
+ }
217
+
218
+ getNamespace();
219
+
220
+ export {
221
+ ICON_NAMES as incIconNames,
222
+ getIconRenderer,
223
+ normalizeIconName,
224
+ renderDefaultIcon,
225
+ renderIncIcon,
226
+ replaceIconContents,
227
+ setIconRenderer,
228
+ upgradeIconPlaceholders,
229
+ };
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -1,3 +1,8 @@
1
+ import {
2
+ replaceIconContents,
3
+ upgradeIconPlaceholders,
4
+ } from "./icons/index.js";
5
+
1
6
  (function () {
2
7
  "use strict";
3
8
 
@@ -12,6 +17,7 @@
12
17
  themeLabel: "[data-inc-theme-label]",
13
18
  themeSwitcher: "[data-inc-theme-switcher], details.inc-theme-switcher",
14
19
  nativeDialogOpen: "[data-inc-native-dialog-open]",
20
+ icon: "[data-inc-icon]",
15
21
  autoRefresh: "[data-inc-auto-refresh]",
16
22
  autoRefreshToggle: '[data-inc-action="auto-refresh-toggle"]',
17
23
  fileExample: "[data-inc-file-example]",
@@ -1132,16 +1138,6 @@
1132
1138
  return `${minutes}m ${seconds}s`;
1133
1139
  }
1134
1140
 
1135
- const AUTO_REFRESH_PAUSE_ICON = `
1136
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
1137
- <path d="M4 3h3v10H4zM9 3h3v10H9z"></path>
1138
- </svg>`.trim();
1139
-
1140
- const AUTO_REFRESH_PLAY_ICON = `
1141
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
1142
- <path d="M4 3.5v9l8-4.5-8-4.5z"></path>
1143
- </svg>`.trim();
1144
-
1145
1141
  function getAutoRefreshParts(root) {
1146
1142
  const toggle = root.querySelector(".inc-auto-refresh__toggle");
1147
1143
 
@@ -1183,7 +1179,11 @@
1183
1179
  }
1184
1180
 
1185
1181
  if (parts.toggleIcon instanceof HTMLElement) {
1186
- parts.toggleIcon.innerHTML = isPaused ? AUTO_REFRESH_PLAY_ICON : AUTO_REFRESH_PAUSE_ICON;
1182
+ replaceIconContents(parts.toggleIcon, isPaused ? "play" : "pause", {
1183
+ className: "inc-icon",
1184
+ decorative: true,
1185
+ size: 16,
1186
+ });
1187
1187
  }
1188
1188
  }
1189
1189
 
@@ -1848,6 +1848,7 @@
1848
1848
  initializeMenus();
1849
1849
  initializeCollapses();
1850
1850
  initializeTabs();
1851
+ upgradeIconPlaceholders(document);
1851
1852
  initializeFileExamples();
1852
1853
  initializeAutoRefresh();
1853
1854
  attachEventHandlers();
@@ -448,6 +448,12 @@
448
448
  // Buttons, badges, alerts, and form primitives
449
449
  // -----------------------------------------------------------------------------
450
450
 
451
+ .inc-icon {
452
+ display: block;
453
+ flex: 0 0 auto;
454
+ stroke: currentColor;
455
+ }
456
+
451
457
  .inc-btn {
452
458
  @extend .btn;
453
459
  display: inline-flex;
@@ -457,6 +463,15 @@
457
463
  vertical-align: middle;
458
464
  transition: color 0.18s ease, background-color 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
459
465
 
466
+ &__icon {
467
+ display: inline-flex;
468
+ align-items: center;
469
+ justify-content: center;
470
+ width: 1rem;
471
+ height: 1rem;
472
+ flex: 0 0 auto;
473
+ }
474
+
460
475
  &--primary {
461
476
  @extend .btn-primary;
462
477
  }
@@ -736,6 +751,17 @@
736
751
  @extend .alert-dismissible;
737
752
  }
738
753
 
754
+ &__icon {
755
+ display: inline-flex;
756
+ align-items: center;
757
+ justify-content: center;
758
+ width: 1.125rem;
759
+ height: 1.125rem;
760
+ margin-inline-end: 0.5rem;
761
+ vertical-align: -0.2em;
762
+ color: currentColor;
763
+ }
764
+
739
765
  &__heading {
740
766
  @extend .alert-heading;
741
767
  }
@@ -1175,6 +1201,12 @@
1175
1201
  display: inline-flex;
1176
1202
  align-items: center;
1177
1203
  justify-content: center;
1204
+
1205
+ svg {
1206
+ display: block;
1207
+ width: 2.125rem;
1208
+ height: 2.125rem;
1209
+ }
1178
1210
  }
1179
1211
 
1180
1212
  &__form {
@@ -3238,7 +3270,6 @@ dialog.inc-native-dialog.inc-native-dialog--drawer .inc-native-dialog__body {
3238
3270
  display: block;
3239
3271
  width: 1rem;
3240
3272
  height: 1rem;
3241
- fill: currentColor;
3242
3273
  }
3243
3274
  }
3244
3275
 
@@ -4644,6 +4675,12 @@ body.inc-offcanvas-open {
4644
4675
  font-size: 1.25rem;
4645
4676
  font-weight: 700;
4646
4677
  flex: 0 0 auto;
4678
+
4679
+ svg {
4680
+ display: block;
4681
+ width: 1.375rem;
4682
+ height: 1.375rem;
4683
+ }
4647
4684
  }
4648
4685
 
4649
4686
  &__title {
@@ -8,9 +8,11 @@ The CSS-first [`inc-*`](../../reference.html) class surface remains the canonica
8
8
 
9
9
  - Package entrypoint: `@incursa/ui-kit/web-components`
10
10
  - Style entrypoint: `@incursa/ui-kit/web-components/style.css`
11
+ - Icon entrypoint: `@incursa/ui-kit/icons`
11
12
  - Built output: `dist/web-components/`
12
13
  - Package export: `./web-components` resolves to `dist/web-components/index.js`
13
14
  - Package export: `./web-components/style.css` resolves to `dist/web-components/style.css`
15
+ - Package export: `./icons` resolves to `dist/icons/index.js`
14
16
  - Module boundary: `src/web-components/package.json` sets this subtree to `type: module`
15
17
 
16
18
  Load these entrypoints only when the consuming app wants the custom elements and their default look. CSS-only consumers should not pay for the runtime.
@@ -45,6 +47,8 @@ The runtime auto-defines the shipped elements on load. If a consumer needs expli
45
47
  The `IncElement` base class, including reflected attribute/property wiring and slot helpers.
46
48
  - [`registry.js`](registry.js)
47
49
  Idempotent registration helpers and the `IncWebComponents.registry` namespace.
50
+ - [`../icons/index.js`](../icons/index.js)
51
+ Semantic Incursa icon names, default Lucide-backed rendering, and the global renderer override used by component fallbacks.
48
52
  - [`components/dom-helpers.js`](components/dom-helpers.js)
49
53
  Shared DOM helpers used by the action and collection modules.
50
54
  - [`controllers/focus.js`](controllers/focus.js)
@@ -1,3 +1,7 @@
1
+ import {
2
+ normalizeIconName,
3
+ replaceIconContents,
4
+ } from "../../icons/index.js";
1
5
  import {
2
6
  addClass,
3
7
  ensureNode,
@@ -28,6 +32,14 @@ const ALERT_DEFAULT_ROLE_BY_TONE = new Map([
28
32
  ["info", "status"],
29
33
  ["secondary", "status"],
30
34
  ]);
35
+ const ALERT_ICON_BY_TONE = new Map([
36
+ ["success", "success"],
37
+ ["danger", "error"],
38
+ ["warning", "warning"],
39
+ ["info", "info"],
40
+ ["secondary", "info"],
41
+ ["primary", "info"],
42
+ ]);
31
43
 
32
44
  const HostElement = typeof HTMLElement === "undefined" ? class {} : HTMLElement;
33
45
 
@@ -53,6 +65,29 @@ function emit(host, type, detail = {}, options = {}) {
53
65
  }));
54
66
  }
55
67
 
68
+ function getDirectIconSlot(host) {
69
+ return Array.from(host.children || []).find((node) => (
70
+ node instanceof HTMLElement
71
+ && node.getAttribute("slot") === "icon"
72
+ )) || null;
73
+ }
74
+
75
+ function hasConsumerIcon(container) {
76
+ return Array.from(container.children || []).some((node) => (
77
+ node instanceof HTMLElement
78
+ && !node.hasAttribute("data-inc-generated-icon")
79
+ ));
80
+ }
81
+
82
+ function renderDecorativeIcon(container, name, options = {}) {
83
+ replaceIconContents(container, name, {
84
+ className: "inc-icon",
85
+ decorative: true,
86
+ size: options.size || 16,
87
+ });
88
+ container.hidden = false;
89
+ }
90
+
56
91
  class IncElement extends HostElement {
57
92
  emit(type, detail = {}, options = {}) {
58
93
  return emit(this, type, detail, options);
@@ -61,7 +96,7 @@ class IncElement extends HostElement {
61
96
 
62
97
  export class IncButtonElement extends IncElement {
63
98
  static get observedAttributes() {
64
- return ["tone", "variant", "size", "loading", "href", "type", "disabled", "label", "target", "rel", "download"];
99
+ return ["tone", "variant", "size", "loading", "href", "type", "disabled", "label", "target", "rel", "download", "icon"];
65
100
  }
66
101
 
67
102
  connectedCallback() {
@@ -166,6 +201,8 @@ export class IncButtonElement extends IncElement {
166
201
  this.removeLoadingSpinner(control);
167
202
  }
168
203
 
204
+ this.syncIcon(control);
205
+
169
206
  const label = this.getAttribute("label");
170
207
  if (label) {
171
208
  control.setAttribute("aria-label", label);
@@ -220,6 +257,53 @@ export class IncButtonElement extends IncElement {
220
257
 
221
258
  control.querySelectorAll(":scope > [data-inc-button-spinner]").forEach((node) => node.remove());
222
259
  }
260
+
261
+ syncIcon(control) {
262
+ if (!(control instanceof HTMLElement)) {
263
+ return;
264
+ }
265
+
266
+ const explicitIcon = normalizeIconName(this.getAttribute("icon"));
267
+ const inferredIcon = this.getAttribute("download") != null
268
+ ? "download"
269
+ : this.getAttribute("target") === "_blank"
270
+ ? "external-link"
271
+ : "";
272
+ const iconName = explicitIcon || inferredIcon;
273
+ let icon = control.querySelector(":scope > [data-inc-button-icon]");
274
+ const slotted = getDirectIconSlot(control);
275
+
276
+ if (!icon && (iconName || slotted)) {
277
+ icon = document.createElement("span");
278
+ icon.className = "inc-btn__icon";
279
+ icon.setAttribute("data-inc-button-icon", "true");
280
+ icon.setAttribute("aria-hidden", "true");
281
+ control.prepend(icon);
282
+ }
283
+
284
+ if (!(icon instanceof HTMLElement)) {
285
+ return;
286
+ }
287
+
288
+ if (slotted) {
289
+ slotted.removeAttribute("slot");
290
+ icon.replaceChildren(slotted);
291
+ icon.hidden = false;
292
+ return;
293
+ }
294
+
295
+ if (hasConsumerIcon(icon)) {
296
+ icon.hidden = false;
297
+ return;
298
+ }
299
+
300
+ if (iconName && iconName !== "none") {
301
+ renderDecorativeIcon(icon, iconName, { size: 16 });
302
+ return;
303
+ }
304
+
305
+ icon.remove();
306
+ }
223
307
  }
224
308
 
225
309
  export class IncButtonGroupElement extends IncElement {
@@ -350,7 +434,7 @@ export class IncCloseButtonElement extends IncElement {
350
434
 
351
435
  export class IncAlertElement extends IncElement {
352
436
  static get observedAttributes() {
353
- return ["tone", "variant", "dismissible", "dismiss-label", "timeout"];
437
+ return ["tone", "variant", "dismissible", "dismiss-label", "timeout", "icon"];
354
438
  }
355
439
 
356
440
  connectedCallback() {
@@ -398,6 +482,7 @@ export class IncAlertElement extends IncElement {
398
482
  const tone = normalizeToken(this.getAttribute("tone") || this.getAttribute("variant")) || "info";
399
483
  const resolvedTone = BADGE_TONES.has(tone) ? tone : "info";
400
484
  this.classList.add(`inc-alert--${resolvedTone}`);
485
+ this.syncIcon(resolvedTone);
401
486
 
402
487
  if (toBoolean(this.getAttribute("dismissible"))) {
403
488
  this.classList.add("inc-alert--dismissible");
@@ -465,6 +550,44 @@ export class IncAlertElement extends IncElement {
465
550
  this.querySelectorAll(":scope > .inc-alert__progress").forEach((node) => node.remove());
466
551
  }
467
552
 
553
+ syncIcon(tone) {
554
+ const explicitIcon = normalizeIconName(this.getAttribute("icon"));
555
+ const iconName = explicitIcon || ALERT_ICON_BY_TONE.get(tone) || "info";
556
+ let icon = this.querySelector(":scope > .inc-alert__icon");
557
+ const slotted = getDirectIconSlot(this);
558
+
559
+ if (!icon && (iconName !== "none" || slotted)) {
560
+ icon = document.createElement("span");
561
+ icon.className = "inc-alert__icon";
562
+ icon.setAttribute("part", "icon");
563
+ icon.setAttribute("aria-hidden", "true");
564
+ this.prepend(icon);
565
+ }
566
+
567
+ if (!(icon instanceof HTMLElement)) {
568
+ return;
569
+ }
570
+
571
+ if (slotted) {
572
+ slotted.removeAttribute("slot");
573
+ icon.replaceChildren(slotted);
574
+ icon.hidden = false;
575
+ return;
576
+ }
577
+
578
+ if (hasConsumerIcon(icon)) {
579
+ icon.hidden = false;
580
+ return;
581
+ }
582
+
583
+ if (iconName === "none") {
584
+ icon.remove();
585
+ return;
586
+ }
587
+
588
+ renderDecorativeIcon(icon, iconName, { size: 18 });
589
+ }
590
+
468
591
  startDismissTimer(timeoutMs) {
469
592
  const progress = this.ensureProgressBar();
470
593
  this.stopDismissTimer();
@@ -514,11 +637,21 @@ export class IncAlertElement extends IncElement {
514
637
  }
515
638
 
516
639
  export class IncEmptyStateElement extends IncElement {
640
+ static get observedAttributes() {
641
+ return ["icon"];
642
+ }
643
+
517
644
  connectedCallback() {
518
645
  addClass(this, "inc-empty-state");
519
646
  this.sync();
520
647
  }
521
648
 
649
+ attributeChangedCallback() {
650
+ if (this.isConnected) {
651
+ this.sync();
652
+ }
653
+ }
654
+
522
655
  sync() {
523
656
  addClass(this, "inc-empty-state");
524
657
  this.setAttribute("part", "empty-state content icon body actions");
@@ -572,6 +705,20 @@ export class IncEmptyStateElement extends IncElement {
572
705
 
573
706
  body.append(node);
574
707
  });
708
+
709
+ if (hasConsumerIcon(icon)) {
710
+ icon.hidden = false;
711
+ return;
712
+ }
713
+
714
+ const iconName = normalizeIconName(this.getAttribute("icon")) || "empty";
715
+ if (iconName === "none") {
716
+ icon.replaceChildren();
717
+ icon.hidden = true;
718
+ return;
719
+ }
720
+
721
+ renderDecorativeIcon(icon, iconName, { size: 34 });
575
722
  }
576
723
  }
577
724