@incursa/ui-kit 1.6.1 → 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.
- package/NOTICE +8 -0
- package/README.md +16 -0
- package/dist/icons/index.js +371 -0
- package/dist/icons/package.json +3 -0
- package/dist/inc-design-language.css +144 -51
- package/dist/inc-design-language.css.map +1 -1
- package/dist/inc-design-language.js +1627 -1206
- package/dist/inc-design-language.min.css +1 -1
- package/dist/inc-design-language.min.css.map +1 -1
- package/dist/mcp/components/buttons.json +3 -3
- package/dist/mcp/components/cards.json +3 -3
- package/dist/mcp/components/metrics.json +3 -3
- package/dist/mcp/components/states.json +3 -3
- package/dist/mcp/components/status.json +3 -3
- package/dist/mcp/examples/data-grid-advanced.json +2 -2
- package/dist/mcp/examples/demo.json +2 -2
- package/dist/mcp/examples/overlay-workflows.json +2 -2
- package/dist/mcp/examples/reference.json +2 -2
- package/dist/mcp/examples/states.json +2 -2
- package/dist/mcp/examples/web-components.json +2 -2
- package/dist/mcp/guides/latest.json +2 -2
- package/dist/mcp/guides/package-metadata.json +2 -2
- package/dist/mcp/guides/update.json +2 -2
- package/dist/mcp/install.json +1 -1
- package/dist/mcp/patterns/data-grid-advanced.json +2 -2
- package/dist/mcp/patterns/demo.json +2 -2
- package/dist/mcp/patterns/overlay-workflows.json +2 -2
- package/dist/mcp/patterns/reference.json +2 -2
- package/dist/mcp/patterns/states.json +2 -2
- package/dist/mcp/patterns/web-components.json +2 -2
- package/dist/mcp/resources.json +83 -80
- package/dist/mcp/search-index.json +25 -25
- package/dist/mcp/update.json +2 -2
- package/dist/mcp/worker.mjs +394 -391
- package/dist/mcp/worker.mjs.map +2 -2
- package/dist/web-components/README.md +4 -0
- package/dist/web-components/components/actions.js +237 -14
- package/dist/web-components/components/feedback.js +71 -7
- package/dist/web-components/index.js +583 -21
- package/package.json +10 -3
- package/src/icons/index.js +229 -0
- package/src/icons/package.json +3 -0
- package/src/inc-design-language.js +327 -1
- package/src/inc-design-language.scss +178 -55
- package/src/web-components/README.md +4 -0
- package/src/web-components/components/actions.js +237 -14
- package/src/web-components/components/feedback.js +71 -7
|
@@ -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,
|
|
@@ -24,6 +28,18 @@ const BUTTON_VARIANTS = new Set([
|
|
|
24
28
|
"outline-info",
|
|
25
29
|
]);
|
|
26
30
|
const BUTTON_SIZES = new Set(["sm", "lg", "micro"]);
|
|
31
|
+
const ALERT_DEFAULT_ROLE_BY_TONE = new Map([
|
|
32
|
+
["info", "status"],
|
|
33
|
+
["secondary", "status"],
|
|
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
|
+
]);
|
|
27
43
|
|
|
28
44
|
const HostElement = typeof HTMLElement === "undefined" ? class {} : HTMLElement;
|
|
29
45
|
|
|
@@ -35,6 +51,11 @@ function toBoolean(value, fallback = false) {
|
|
|
35
51
|
return !FALSE_TOKENS.has(String(value).toLowerCase());
|
|
36
52
|
}
|
|
37
53
|
|
|
54
|
+
function toPositiveInt(value) {
|
|
55
|
+
const parsed = Number.parseInt(String(value ?? "").trim(), 10);
|
|
56
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
38
59
|
function emit(host, type, detail = {}, options = {}) {
|
|
39
60
|
return host.dispatchEvent(new CustomEvent(type, {
|
|
40
61
|
detail,
|
|
@@ -44,6 +65,29 @@ function emit(host, type, detail = {}, options = {}) {
|
|
|
44
65
|
}));
|
|
45
66
|
}
|
|
46
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
|
+
|
|
47
91
|
class IncElement extends HostElement {
|
|
48
92
|
emit(type, detail = {}, options = {}) {
|
|
49
93
|
return emit(this, type, detail, options);
|
|
@@ -52,7 +96,7 @@ class IncElement extends HostElement {
|
|
|
52
96
|
|
|
53
97
|
export class IncButtonElement extends IncElement {
|
|
54
98
|
static get observedAttributes() {
|
|
55
|
-
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"];
|
|
56
100
|
}
|
|
57
101
|
|
|
58
102
|
connectedCallback() {
|
|
@@ -157,6 +201,8 @@ export class IncButtonElement extends IncElement {
|
|
|
157
201
|
this.removeLoadingSpinner(control);
|
|
158
202
|
}
|
|
159
203
|
|
|
204
|
+
this.syncIcon(control);
|
|
205
|
+
|
|
160
206
|
const label = this.getAttribute("label");
|
|
161
207
|
if (label) {
|
|
162
208
|
control.setAttribute("aria-label", label);
|
|
@@ -211,6 +257,53 @@ export class IncButtonElement extends IncElement {
|
|
|
211
257
|
|
|
212
258
|
control.querySelectorAll(":scope > [data-inc-button-spinner]").forEach((node) => node.remove());
|
|
213
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
|
+
}
|
|
214
307
|
}
|
|
215
308
|
|
|
216
309
|
export class IncButtonGroupElement extends IncElement {
|
|
@@ -292,7 +385,6 @@ export class IncCloseButtonElement extends IncElement {
|
|
|
292
385
|
}
|
|
293
386
|
|
|
294
387
|
connectedCallback() {
|
|
295
|
-
addClass(this, "inc-close-button");
|
|
296
388
|
this.sync();
|
|
297
389
|
}
|
|
298
390
|
|
|
@@ -303,7 +395,7 @@ export class IncCloseButtonElement extends IncElement {
|
|
|
303
395
|
}
|
|
304
396
|
|
|
305
397
|
sync() {
|
|
306
|
-
|
|
398
|
+
this.classList.remove("inc-close-button", "inc-close-button--white");
|
|
307
399
|
this.setAttribute("part", "close-button");
|
|
308
400
|
const control = this.ensureControl();
|
|
309
401
|
const variant = normalizeToken(this.getAttribute("variant"));
|
|
@@ -316,9 +408,7 @@ export class IncCloseButtonElement extends IncElement {
|
|
|
316
408
|
|
|
317
409
|
control.type = "button";
|
|
318
410
|
control.setAttribute("aria-label", this.getAttribute("label") || "Close");
|
|
319
|
-
|
|
320
|
-
control.textContent = "×";
|
|
321
|
-
}
|
|
411
|
+
control.textContent = "";
|
|
322
412
|
}
|
|
323
413
|
|
|
324
414
|
ensureControl() {
|
|
@@ -344,7 +434,7 @@ export class IncCloseButtonElement extends IncElement {
|
|
|
344
434
|
|
|
345
435
|
export class IncAlertElement extends IncElement {
|
|
346
436
|
static get observedAttributes() {
|
|
347
|
-
return ["tone", "variant", "dismissible", "dismiss-label"];
|
|
437
|
+
return ["tone", "variant", "dismissible", "dismiss-label", "timeout", "icon"];
|
|
348
438
|
}
|
|
349
439
|
|
|
350
440
|
connectedCallback() {
|
|
@@ -354,6 +444,7 @@ export class IncAlertElement extends IncElement {
|
|
|
354
444
|
}
|
|
355
445
|
|
|
356
446
|
disconnectedCallback() {
|
|
447
|
+
this.stopDismissTimer();
|
|
357
448
|
if (this._boundClick) {
|
|
358
449
|
this.removeEventListener("click", this._boundClick);
|
|
359
450
|
}
|
|
@@ -377,7 +468,7 @@ export class IncAlertElement extends IncElement {
|
|
|
377
468
|
}
|
|
378
469
|
|
|
379
470
|
event.preventDefault();
|
|
380
|
-
this.
|
|
471
|
+
this.dismiss("manual");
|
|
381
472
|
};
|
|
382
473
|
|
|
383
474
|
this.addEventListener("click", this._boundClick);
|
|
@@ -391,6 +482,7 @@ export class IncAlertElement extends IncElement {
|
|
|
391
482
|
const tone = normalizeToken(this.getAttribute("tone") || this.getAttribute("variant")) || "info";
|
|
392
483
|
const resolvedTone = BADGE_TONES.has(tone) ? tone : "info";
|
|
393
484
|
this.classList.add(`inc-alert--${resolvedTone}`);
|
|
485
|
+
this.syncIcon(resolvedTone);
|
|
394
486
|
|
|
395
487
|
if (toBoolean(this.getAttribute("dismissible"))) {
|
|
396
488
|
this.classList.add("inc-alert--dismissible");
|
|
@@ -400,12 +492,25 @@ export class IncAlertElement extends IncElement {
|
|
|
400
492
|
}
|
|
401
493
|
|
|
402
494
|
if (!this.hasAttribute("role")) {
|
|
403
|
-
this.setAttribute("role", resolvedTone
|
|
495
|
+
this.setAttribute("role", ALERT_DEFAULT_ROLE_BY_TONE.get(resolvedTone) || "alert");
|
|
404
496
|
}
|
|
405
497
|
if (!this.hasAttribute("aria-live")) {
|
|
406
498
|
this.setAttribute("aria-live", this.getAttribute("role") === "alert" ? "assertive" : "polite");
|
|
407
499
|
}
|
|
408
500
|
this.setAttribute("aria-atomic", "true");
|
|
501
|
+
|
|
502
|
+
const timeoutMs = toPositiveInt(this.getAttribute("timeout"));
|
|
503
|
+
if (timeoutMs) {
|
|
504
|
+
this.ensureProgressBar();
|
|
505
|
+
if (!this.hidden && this.getAttribute("aria-hidden") !== "true") {
|
|
506
|
+
this.startDismissTimer(timeoutMs);
|
|
507
|
+
} else {
|
|
508
|
+
this.stopDismissTimer();
|
|
509
|
+
}
|
|
510
|
+
} else {
|
|
511
|
+
this.stopDismissTimer();
|
|
512
|
+
this.removeProgressBar();
|
|
513
|
+
}
|
|
409
514
|
}
|
|
410
515
|
|
|
411
516
|
ensureDismissButton() {
|
|
@@ -420,9 +525,7 @@ export class IncAlertElement extends IncElement {
|
|
|
420
525
|
button.className = "inc-close-button";
|
|
421
526
|
button.setAttribute("part", "dismiss");
|
|
422
527
|
button.setAttribute("aria-label", this.getAttribute("dismiss-label") || "Dismiss alert");
|
|
423
|
-
|
|
424
|
-
button.textContent = "×";
|
|
425
|
-
}
|
|
528
|
+
button.textContent = "";
|
|
426
529
|
return button;
|
|
427
530
|
}
|
|
428
531
|
|
|
@@ -430,19 +533,125 @@ export class IncAlertElement extends IncElement {
|
|
|
430
533
|
this.querySelectorAll(":scope > [data-inc-alert-dismiss]").forEach((node) => node.remove());
|
|
431
534
|
}
|
|
432
535
|
|
|
433
|
-
|
|
536
|
+
ensureProgressBar() {
|
|
537
|
+
let progress = this.querySelector(":scope > .inc-alert__progress");
|
|
538
|
+
if (!progress) {
|
|
539
|
+
progress = document.createElement("div");
|
|
540
|
+
progress.className = "inc-alert__progress";
|
|
541
|
+
progress.setAttribute("part", "progress");
|
|
542
|
+
progress.setAttribute("aria-hidden", "true");
|
|
543
|
+
this.append(progress);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return progress;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
removeProgressBar() {
|
|
550
|
+
this.querySelectorAll(":scope > .inc-alert__progress").forEach((node) => node.remove());
|
|
551
|
+
}
|
|
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
|
+
|
|
591
|
+
startDismissTimer(timeoutMs) {
|
|
592
|
+
const progress = this.ensureProgressBar();
|
|
593
|
+
this.stopDismissTimer();
|
|
594
|
+
this._dismissTimeoutMs = timeoutMs;
|
|
595
|
+
this._dismissStartedAt = performance.now();
|
|
596
|
+
|
|
597
|
+
const tick = (now) => {
|
|
598
|
+
if (this.hidden || this.getAttribute("aria-hidden") === "true") {
|
|
599
|
+
this.stopDismissTimer();
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const elapsed = Math.max(0, now - this._dismissStartedAt);
|
|
604
|
+
const remaining = Math.max(0, timeoutMs - elapsed);
|
|
605
|
+
const ratio = timeoutMs > 0 ? remaining / timeoutMs : 0;
|
|
606
|
+
progress.style.transform = `scaleX(${ratio})`;
|
|
607
|
+
|
|
608
|
+
if (remaining <= 0) {
|
|
609
|
+
this.dismiss("timeout");
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
this._dismissFrame = window.requestAnimationFrame(tick);
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
progress.style.transform = "scaleX(1)";
|
|
617
|
+
this._dismissFrame = window.requestAnimationFrame(tick);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
stopDismissTimer() {
|
|
621
|
+
if (this._dismissFrame) {
|
|
622
|
+
window.cancelAnimationFrame(this._dismissFrame);
|
|
623
|
+
this._dismissFrame = 0;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
dismiss(reason = "manual") {
|
|
628
|
+
this.hide(reason);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
hide(reason = "manual") {
|
|
632
|
+
this.stopDismissTimer();
|
|
434
633
|
this.hidden = true;
|
|
435
634
|
this.setAttribute("aria-hidden", "true");
|
|
436
|
-
this.emit("dismiss", { hidden: true });
|
|
635
|
+
this.emit("dismiss", { hidden: true, reason });
|
|
437
636
|
}
|
|
438
637
|
}
|
|
439
638
|
|
|
440
639
|
export class IncEmptyStateElement extends IncElement {
|
|
640
|
+
static get observedAttributes() {
|
|
641
|
+
return ["icon"];
|
|
642
|
+
}
|
|
643
|
+
|
|
441
644
|
connectedCallback() {
|
|
442
645
|
addClass(this, "inc-empty-state");
|
|
443
646
|
this.sync();
|
|
444
647
|
}
|
|
445
648
|
|
|
649
|
+
attributeChangedCallback() {
|
|
650
|
+
if (this.isConnected) {
|
|
651
|
+
this.sync();
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
446
655
|
sync() {
|
|
447
656
|
addClass(this, "inc-empty-state");
|
|
448
657
|
this.setAttribute("part", "empty-state content icon body actions");
|
|
@@ -496,6 +705,20 @@ export class IncEmptyStateElement extends IncElement {
|
|
|
496
705
|
|
|
497
706
|
body.append(node);
|
|
498
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 });
|
|
499
722
|
}
|
|
500
723
|
}
|
|
501
724
|
|
|
@@ -1,7 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
incIconNames,
|
|
3
|
+
normalizeIconName,
|
|
4
|
+
replaceIconContents,
|
|
5
|
+
} from "../../icons/index.js";
|
|
6
|
+
|
|
1
7
|
const THEME_MODES = ["light", "dark", "system"];
|
|
2
8
|
const DEFAULT_THEME_STORAGE_KEY = "inc-theme-mode";
|
|
3
9
|
const BADGE_TONES = new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
|
|
4
10
|
const SPINNER_VARIANTS = new Set(["border", "grow"]);
|
|
11
|
+
const ICON_NAME_SET = new Set(incIconNames);
|
|
12
|
+
const STATE_ICON_BY_VARIANT = new Map([
|
|
13
|
+
["empty", "empty"],
|
|
14
|
+
["results", "no-results"],
|
|
15
|
+
["loading", "loading"],
|
|
16
|
+
["error", "error"],
|
|
17
|
+
["danger", "error"],
|
|
18
|
+
["warning", "warning"],
|
|
19
|
+
["success", "success"],
|
|
20
|
+
["info", "info"],
|
|
21
|
+
]);
|
|
22
|
+
const STATE_ICON_BY_STATUS = new Map([
|
|
23
|
+
["+", "empty"],
|
|
24
|
+
["?", "no-results"],
|
|
25
|
+
["!", "error"],
|
|
26
|
+
["...", "loading"],
|
|
27
|
+
]);
|
|
5
28
|
const HostElement = typeof HTMLElement === "undefined" ? class {} : HTMLElement;
|
|
6
29
|
const themeSubscribers = new Set();
|
|
7
30
|
|
|
@@ -38,6 +61,33 @@ function normalizeToken(value) {
|
|
|
38
61
|
return String(value ?? "").trim().toLowerCase();
|
|
39
62
|
}
|
|
40
63
|
|
|
64
|
+
function resolveStateIconName(icon, status, variant) {
|
|
65
|
+
const explicitIcon = normalizeIconName(icon);
|
|
66
|
+
if (explicitIcon) {
|
|
67
|
+
return explicitIcon;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const normalizedStatus = normalizeIconName(status);
|
|
71
|
+
if (STATE_ICON_BY_STATUS.has(String(status || "").trim())) {
|
|
72
|
+
return STATE_ICON_BY_STATUS.get(String(status || "").trim());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (ICON_NAME_SET.has(normalizedStatus)) {
|
|
76
|
+
return normalizedStatus;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return STATE_ICON_BY_VARIANT.get(normalizeToken(variant)) || "info";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function renderDecorativeIcon(container, name, size = 18) {
|
|
83
|
+
replaceIconContents(container, name, {
|
|
84
|
+
className: "inc-icon",
|
|
85
|
+
decorative: true,
|
|
86
|
+
size,
|
|
87
|
+
});
|
|
88
|
+
container.hidden = false;
|
|
89
|
+
}
|
|
90
|
+
|
|
41
91
|
function getSystemTheme() {
|
|
42
92
|
if (!window.matchMedia) {
|
|
43
93
|
return "light";
|
|
@@ -198,7 +248,7 @@ function formatRemaining(totalSeconds) {
|
|
|
198
248
|
}
|
|
199
249
|
|
|
200
250
|
export class IncStatePanel extends HostElement {
|
|
201
|
-
static observedAttributes = ["tone", "variant", "title", "body", "status", "open"];
|
|
251
|
+
static observedAttributes = ["tone", "variant", "title", "body", "status", "icon", "open"];
|
|
202
252
|
|
|
203
253
|
#fallback = null;
|
|
204
254
|
#appliedVariantClass = "";
|
|
@@ -238,6 +288,7 @@ export class IncStatePanel extends HostElement {
|
|
|
238
288
|
actions.className = "inc-state-panel__actions";
|
|
239
289
|
|
|
240
290
|
icon.setAttribute("part", "icon");
|
|
291
|
+
icon.setAttribute("aria-hidden", "true");
|
|
241
292
|
title.setAttribute("part", "title");
|
|
242
293
|
body.setAttribute("part", "body");
|
|
243
294
|
actions.setAttribute("part", "actions");
|
|
@@ -274,11 +325,19 @@ export class IncStatePanel extends HostElement {
|
|
|
274
325
|
const title = this.getAttribute("title") || "";
|
|
275
326
|
const body = this.getAttribute("body") || "";
|
|
276
327
|
const status = this.getAttribute("status") || "";
|
|
328
|
+
const iconName = resolveStateIconName(this.getAttribute("icon"), status, nextVariant);
|
|
277
329
|
|
|
278
330
|
this.#fallback.title.textContent = title;
|
|
279
331
|
this.#fallback.body.textContent = body;
|
|
280
|
-
|
|
281
|
-
|
|
332
|
+
if (iconName === "none") {
|
|
333
|
+
this.#fallback.icon.replaceChildren();
|
|
334
|
+
this.#fallback.icon.hidden = true;
|
|
335
|
+
} else if (ICON_NAME_SET.has(iconName)) {
|
|
336
|
+
renderDecorativeIcon(this.#fallback.icon, iconName, 22);
|
|
337
|
+
} else {
|
|
338
|
+
this.#fallback.icon.textContent = status;
|
|
339
|
+
this.#fallback.icon.hidden = !status;
|
|
340
|
+
}
|
|
282
341
|
this.#fallback.actions.hidden = true;
|
|
283
342
|
}
|
|
284
343
|
|
|
@@ -677,6 +736,10 @@ export class IncAutoRefresh extends HostElement {
|
|
|
677
736
|
}
|
|
678
737
|
|
|
679
738
|
this.innerHTML = `
|
|
739
|
+
<button type="button" class="inc-auto-refresh__toggle inc-btn inc-btn--outline-secondary inc-btn--micro" part="toggle">
|
|
740
|
+
<span class="inc-auto-refresh__toggle-icon" aria-hidden="true"></span>
|
|
741
|
+
<span class="inc-auto-refresh__toggle-text"></span>
|
|
742
|
+
</button>
|
|
680
743
|
<span class="inc-auto-refresh__countdown" part="countdown">
|
|
681
744
|
<span class="inc-auto-refresh__label" part="label"></span>
|
|
682
745
|
<span class="inc-auto-refresh__value" part="value"></span>
|
|
@@ -684,10 +747,6 @@ export class IncAutoRefresh extends HostElement {
|
|
|
684
747
|
<span class="inc-auto-refresh__status" part="status" hidden>
|
|
685
748
|
<span class="inc-auto-refresh__status-text"></span>
|
|
686
749
|
</span>
|
|
687
|
-
<button type="button" class="inc-auto-refresh__toggle inc-btn inc-btn--outline-secondary inc-btn--micro" part="toggle">
|
|
688
|
-
<span class="inc-auto-refresh__toggle-icon" aria-hidden="true"></span>
|
|
689
|
-
<span class="inc-auto-refresh__toggle-text"></span>
|
|
690
|
-
</button>
|
|
691
750
|
`.trim();
|
|
692
751
|
|
|
693
752
|
this.#parts = this.#getParts();
|
|
@@ -701,6 +760,7 @@ export class IncAutoRefresh extends HostElement {
|
|
|
701
760
|
status: this.querySelector(".inc-auto-refresh__status"),
|
|
702
761
|
statusText: this.querySelector(".inc-auto-refresh__status-text"),
|
|
703
762
|
toggle: this.querySelector(".inc-auto-refresh__toggle"),
|
|
763
|
+
toggleIcon: this.querySelector(".inc-auto-refresh__toggle-icon"),
|
|
704
764
|
toggleText: this.querySelector(".inc-auto-refresh__toggle-text"),
|
|
705
765
|
};
|
|
706
766
|
}
|
|
@@ -861,6 +921,10 @@ export class IncAutoRefresh extends HostElement {
|
|
|
861
921
|
if (this.#parts.toggleText) {
|
|
862
922
|
this.#parts.toggleText.textContent = actionLabel;
|
|
863
923
|
}
|
|
924
|
+
|
|
925
|
+
if (this.#parts.toggleIcon instanceof HTMLElement) {
|
|
926
|
+
renderDecorativeIcon(this.#parts.toggleIcon, this.#isPaused ? "play" : "pause", 16);
|
|
927
|
+
}
|
|
864
928
|
}
|
|
865
929
|
|
|
866
930
|
#stop() {
|