@dataengineeringformachinelearning/viking-ui 3.0.0-alpha.2 → 4.0.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 (198) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +55 -12
  3. package/dist/LICENSE +201 -0
  4. package/dist/README.md +251 -0
  5. package/dist/fesm2022/dataengineeringformachinelearning-viking-ui.mjs +13751 -0
  6. package/dist/fesm2022/dataengineeringformachinelearning-viking-ui.mjs.map +1 -0
  7. package/dist/index.js +1 -4051
  8. package/dist/package.json +87 -0
  9. package/dist/scripts/build-web-components.mjs +37 -0
  10. package/dist/src/tokens/series-presets.ts +15 -0
  11. package/dist/src/tokens/tailwind.preset.js +209 -0
  12. package/dist/src/tokens/viking-tokens.json +318 -0
  13. package/dist/src/web/badge/viking-badge-wc.ts +116 -0
  14. package/dist/src/web/button/viking-button-wc.ts +165 -0
  15. package/dist/src/web/callout/viking-callout-wc.ts +105 -0
  16. package/dist/src/web/card/viking-card-wc.ts +66 -0
  17. package/dist/src/web/core/base.ts +56 -0
  18. package/dist/src/web/core/dom.ts +53 -0
  19. package/dist/src/web/core/icons-inline.ts +44 -0
  20. package/dist/src/web/core/styles.ts +1138 -0
  21. package/dist/src/web/core/types.ts +19 -0
  22. package/dist/src/web/field/viking-field-wc.ts +154 -0
  23. package/dist/src/web/index.ts +73 -0
  24. package/dist/src/web/input/viking-input-wc.ts +211 -0
  25. package/dist/src/web/modal/viking-modal-wc.ts +187 -0
  26. package/dist/src/web/search-palette/viking-search-palette-wc.ts +494 -0
  27. package/dist/src/web/select/viking-select-wc.ts +218 -0
  28. package/dist/src/web/suite-header/viking-suite-header-wc.ts +714 -0
  29. package/dist/src/web/suite-search-palette/viking-suite-search-palette-wc.ts +234 -0
  30. package/dist/src/web/theme-toggle/viking-theme-toggle-wc.ts +139 -0
  31. package/dist/src/web/types.ts +37 -0
  32. package/dist/src/web/web-components.spec.ts +297 -0
  33. package/dist/types/dataengineeringformachinelearning-viking-ui.d.ts +2658 -0
  34. package/dist/viking.manifest.json +271 -0
  35. package/dist/web-components.js +2427 -126
  36. package/package.json +25 -11
  37. package/src/assets/drakkar-favicon.svg +17 -0
  38. package/src/core/brand/deml-lockup.svg +5 -0
  39. package/src/core/brand/deml-mark-compact.svg +4 -0
  40. package/src/core/brand/deml-mark-filled.svg +3 -0
  41. package/src/core/brand/deml-mark-outline.svg +4 -0
  42. package/src/core/brand-icons.ts +62 -0
  43. package/src/core/icons.spec.ts +52 -0
  44. package/src/core/icons.ts +206 -0
  45. package/src/core/integration-brand-icons.ts +77 -0
  46. package/src/core/lucide-paths.generated.ts +130 -0
  47. package/src/core/types.ts +85 -0
  48. package/src/core/uid.ts +7 -0
  49. package/src/lib/accordion/accordion.ts +126 -0
  50. package/src/lib/app-header/app-header.scss +274 -0
  51. package/src/lib/app-header/app-header.ts +43 -0
  52. package/src/lib/app-sidebar/app-sidebar.scss +415 -0
  53. package/src/lib/app-sidebar/app-sidebar.ts +29 -0
  54. package/src/lib/auth-panel/auth-panel.spec.ts +132 -0
  55. package/src/lib/auth-panel/auth-panel.ts +228 -0
  56. package/src/lib/autocomplete/autocomplete.ts +223 -0
  57. package/src/lib/avatar/avatar.ts +126 -0
  58. package/src/lib/badge/badge.ts +182 -0
  59. package/src/lib/bar/bar.ts +31 -0
  60. package/src/lib/brand/brand.ts +60 -0
  61. package/src/lib/breadcrumbs/breadcrumbs.ts +81 -0
  62. package/src/lib/button/button-group.ts +40 -0
  63. package/src/lib/button/button.scss +96 -0
  64. package/src/lib/button/button.ts +98 -0
  65. package/src/lib/calendar/calendar.ts +253 -0
  66. package/src/lib/callout/callout.ts +211 -0
  67. package/src/lib/card/card-title.spec.ts +27 -0
  68. package/src/lib/card/card-title.ts +50 -0
  69. package/src/lib/card/card.ts +170 -0
  70. package/src/lib/carousel/carousel.ts +206 -0
  71. package/src/lib/chart/chart-zoom.spec.ts +65 -0
  72. package/src/lib/chart/chart-zoom.ts +71 -0
  73. package/src/lib/chart/chart.ts +1257 -0
  74. package/src/lib/chart-card-header/chart-card-header.ts +80 -0
  75. package/src/lib/chart-empty-state/chart-empty-state.ts +146 -0
  76. package/src/lib/chart-panel/chart-panel.ts +133 -0
  77. package/src/lib/checkbox/checkbox.ts +139 -0
  78. package/src/lib/color-picker/color-picker.ts +148 -0
  79. package/src/lib/command/command.ts +260 -0
  80. package/src/lib/composer/composer.ts +180 -0
  81. package/src/lib/confirm-dialog/confirm-dialog.ts +163 -0
  82. package/src/lib/confirm-dialog/dialog.service.ts +79 -0
  83. package/src/lib/context/context.ts +75 -0
  84. package/src/lib/core/cva.ts +33 -0
  85. package/src/lib/date-picker/date-picker.ts +139 -0
  86. package/src/lib/dropdown/dropdown.ts +188 -0
  87. package/src/lib/editor/editor.ts +170 -0
  88. package/src/lib/empty-state/empty-state.ts +100 -0
  89. package/src/lib/fab/fab.ts +76 -0
  90. package/src/lib/field/field.ts +111 -0
  91. package/src/lib/file-upload/file-upload.ts +208 -0
  92. package/src/lib/footer/footer.scss +155 -0
  93. package/src/lib/footer/footer.ts +28 -0
  94. package/src/lib/form-section/form-section.ts +200 -0
  95. package/src/lib/forms.spec.ts +92 -0
  96. package/src/lib/gauge-arc/gauge-arc.ts +85 -0
  97. package/src/lib/hud-panel/hud-panel.ts +27 -0
  98. package/src/lib/icon/icon.ts +284 -0
  99. package/src/lib/icon-badge/icon-badge.ts +87 -0
  100. package/src/lib/icon-heading/icon-heading.spec.ts +32 -0
  101. package/src/lib/icon-heading/icon-heading.ts +38 -0
  102. package/src/lib/icon-text/icon-text.ts +120 -0
  103. package/src/lib/input/input.ts +119 -0
  104. package/src/lib/kanban/kanban.ts +211 -0
  105. package/src/lib/kbd/kbd.ts +32 -0
  106. package/src/lib/label/label.ts +39 -0
  107. package/src/lib/loading-overlay/loading-overlay.ts +80 -0
  108. package/src/lib/menubar/menubar.ts +77 -0
  109. package/src/lib/metric-card/metric-card.ts +33 -0
  110. package/src/lib/modal/modal.ts +207 -0
  111. package/src/lib/native-select/native-select.ts +124 -0
  112. package/src/lib/navbar/navbar.ts +124 -0
  113. package/src/lib/navigation-menu/navigation-menu.ts +116 -0
  114. package/src/lib/otp-input/otp-input.ts +164 -0
  115. package/src/lib/page-back-link/page-back-link.ts +74 -0
  116. package/src/lib/page-header/page-header.ts +70 -0
  117. package/src/lib/pagination/pagination.ts +157 -0
  118. package/src/lib/pillbox/pillbox.ts +157 -0
  119. package/src/lib/popover/popover.ts +92 -0
  120. package/src/lib/profile/profile.ts +73 -0
  121. package/src/lib/progress/progress.ts +123 -0
  122. package/src/lib/radio/radio.ts +132 -0
  123. package/src/lib/ring-gauge/ring-gauge.ts +75 -0
  124. package/src/lib/scroll-area/scroll-area.ts +57 -0
  125. package/src/lib/search-palette/search-palette.scss +288 -0
  126. package/src/lib/search-palette/search-palette.ts +114 -0
  127. package/src/lib/select/select.ts +292 -0
  128. package/src/lib/separator/separator.ts +59 -0
  129. package/src/lib/sheet/sheet.ts +192 -0
  130. package/src/lib/sidebar-nav/sidebar-nav.scss +218 -0
  131. package/src/lib/sidebar-nav/sidebar-nav.ts +158 -0
  132. package/src/lib/site-drakkar/site-footer.ts +129 -0
  133. package/src/lib/site-drakkar/site-navbar.scss +2 -0
  134. package/src/lib/site-drakkar/site-navbar.ts +267 -0
  135. package/src/lib/site-drakkar/suite-search-items.spec.ts +61 -0
  136. package/src/lib/skeleton/skeleton.ts +51 -0
  137. package/src/lib/slider/slider.ts +133 -0
  138. package/src/lib/spinner/spinner.ts +75 -0
  139. package/src/lib/status-metric-row/status-metric-row.ts +74 -0
  140. package/src/lib/suite-search-palette/suite-search-palette.ts +95 -0
  141. package/src/lib/switch/switch.ts +131 -0
  142. package/src/lib/table/table.ts +137 -0
  143. package/src/lib/tabs/tab-panel.ts +61 -0
  144. package/src/lib/tabs/tab.ts +107 -0
  145. package/src/lib/tabs/tabs.ts +109 -0
  146. package/src/lib/textarea/textarea.ts +103 -0
  147. package/src/lib/theme-toggle/theme-toggle.spec.ts +40 -0
  148. package/src/lib/theme-toggle/theme-toggle.ts +39 -0
  149. package/src/lib/time-picker/time-picker.ts +197 -0
  150. package/src/lib/timeline/timeline.ts +152 -0
  151. package/src/lib/toast/toast.ts +196 -0
  152. package/src/lib/toggle/toggle-group.ts +60 -0
  153. package/src/lib/toggle/toggle.ts +101 -0
  154. package/src/lib/tooltip/tooltip.ts +119 -0
  155. package/src/lib/typography/heading.ts +52 -0
  156. package/src/lib/typography/text.ts +51 -0
  157. package/src/lib/uptime-bar/uptime-bar.ts +55 -0
  158. package/src/lib/verification-code-field/verification-code-field.ts +149 -0
  159. package/src/lib/whitepaper-cta/whitepaper-cta.scss +130 -0
  160. package/src/lib/whitepaper-cta/whitepaper-cta.ts +78 -0
  161. package/src/lib/wizard/wizard.ts +80 -0
  162. package/src/public-api.ts +172 -0
  163. package/src/tokens/series-presets.ts +15 -0
  164. package/src/tokens/tailwind.preset.js +209 -0
  165. package/src/viking-ui.spec.ts +416 -0
  166. package/src/web/web-components.spec.ts +297 -0
  167. package/dist/elements/button/viking-button.d.ts +0 -14
  168. package/dist/elements/card/viking-card.d.ts +0 -8
  169. package/dist/elements/core/dom.d.ts +0 -4
  170. package/dist/elements/core/styles.d.ts +0 -2
  171. package/dist/elements/modal/viking-modal.d.ts +0 -15
  172. package/dist/index.d.ts +0 -1
  173. package/dist/lib/core/brand-icons.d.ts +0 -29
  174. package/dist/lib/core/icons.d.ts +0 -132
  175. package/dist/lib/core/integration-brand-icons.d.ts +0 -18
  176. package/dist/lib/core/lucide-paths.generated.d.ts +0 -90
  177. package/dist/lib/site-drakkar/site-drakkar.config.d.ts +0 -50
  178. package/dist/lib/site-drakkar/suite-search-items.d.ts +0 -17
  179. package/dist/web/badge/viking-badge-wc.d.ts +0 -28
  180. package/dist/web/button/viking-button-wc.d.ts +0 -28
  181. package/dist/web/callout/viking-callout-wc.d.ts +0 -27
  182. package/dist/web/card/viking-card-wc.d.ts +0 -29
  183. package/dist/web/core/base.d.ts +0 -12
  184. package/dist/web/core/dom.d.ts +0 -11
  185. package/dist/web/core/icons-inline.d.ts +0 -7
  186. package/dist/web/core/styles.d.ts +0 -12
  187. package/dist/web/core/types.d.ts +0 -10
  188. package/dist/web/field/viking-field-wc.d.ts +0 -32
  189. package/dist/web/index.d.ts +0 -15
  190. package/dist/web/input/viking-input-wc.d.ts +0 -32
  191. package/dist/web/modal/viking-modal-wc.d.ts +0 -50
  192. package/dist/web/search-palette/viking-search-palette-wc.d.ts +0 -64
  193. package/dist/web/select/viking-select-wc.d.ts +0 -48
  194. package/dist/web/suite-header/viking-suite-header-wc.d.ts +0 -36
  195. package/dist/web/suite-search-palette/viking-suite-search-palette-wc.d.ts +0 -39
  196. package/dist/web/theme-toggle/viking-theme-toggle-wc.d.ts +0 -19
  197. package/dist/web/types.d.ts +0 -40
  198. package/dist/web-components/index.d.ts +0 -1
@@ -0,0 +1,116 @@
1
+ import { attachShadowStyles } from "../core/base";
2
+ import {
3
+ defineCustomElement,
4
+ defineCustomElementAlias,
5
+ HTMLElementBase,
6
+ } from "../core/dom";
7
+ import { renderInlineIcon, TONE_ICON_NAMES } from "../core/icons-inline";
8
+ import { VIKING_BADGE_STYLES } from "../core/styles";
9
+ import type { VikingWcTone } from "../core/types";
10
+
11
+ const TONES = new Set<VikingWcTone>([
12
+ "accent",
13
+ "secondary",
14
+ "success",
15
+ "warning",
16
+ "danger",
17
+ "info",
18
+ "muted",
19
+ "subtle",
20
+ ]);
21
+
22
+ /**
23
+ * Framework-agnostic Viking badge Web Component.
24
+ * Tag: `viking-badge` (legacy alias: `viking-badge-wc`)
25
+ *
26
+ * @attr tone - Semantic color: accent | secondary | success | warning | danger | info | muted
27
+ * @attr size - Compact density: sm
28
+ * @attr icon - Viking icon registry name (pairs with label for a11y)
29
+ * @attr removable - Shows remove control; dispatches `viking-removed`
30
+ *
31
+ * @example
32
+ * <viking-badge-wc tone="success" icon="check">Healthy</viking-badge-wc>
33
+ */
34
+ export class VikingBadgeWc extends HTMLElementBase {
35
+ static readonly tag = "viking-badge";
36
+ static readonly legacyTag = "viking-badge-wc";
37
+
38
+ static get observedAttributes(): string[] {
39
+ return ["tone", "size", "icon", "removable"];
40
+ }
41
+
42
+ private readonly shadow: ShadowRoot;
43
+
44
+ constructor() {
45
+ super();
46
+ this.shadow = this.attachShadow({ mode: "open" });
47
+ attachShadowStyles(this.shadow, VIKING_BADGE_STYLES);
48
+ }
49
+
50
+ connectedCallback(): void {
51
+ this.render();
52
+ }
53
+
54
+ attributeChangedCallback(): void {
55
+ if (this.isConnected) {
56
+ this.render();
57
+ }
58
+ }
59
+
60
+ private get tone(): VikingWcTone | null {
61
+ const value = (this.getAttribute("tone") ?? "") as VikingWcTone;
62
+ return TONES.has(value) ? value : null;
63
+ }
64
+
65
+ private get size(): "sm" | null {
66
+ return this.getAttribute("size") === "sm" ? "sm" : null;
67
+ }
68
+
69
+ private get removable(): boolean {
70
+ return (
71
+ this.hasAttribute("removable") &&
72
+ this.getAttribute("removable") !== "false"
73
+ );
74
+ }
75
+
76
+ private readonly onRemove = (): void => {
77
+ this.dispatchEvent(
78
+ new CustomEvent("viking-removed", { bubbles: true, composed: true }),
79
+ );
80
+ };
81
+
82
+ private render(): void {
83
+ const tone = this.tone;
84
+ if (tone && this.getAttribute("tone") !== tone) {
85
+ this.setAttribute("tone", tone);
86
+ } else if (!tone && this.hasAttribute("tone")) {
87
+ this.removeAttribute("tone");
88
+ }
89
+
90
+ const size = this.size;
91
+ if (size && this.getAttribute("size") !== size) {
92
+ this.setAttribute("size", size);
93
+ } else if (!size && this.hasAttribute("size")) {
94
+ this.removeAttribute("size");
95
+ }
96
+
97
+ const iconName =
98
+ this.getAttribute("icon") ?? (tone ? TONE_ICON_NAMES[tone] : null);
99
+ const iconMarkup = iconName ? renderInlineIcon(iconName, 16) : "";
100
+
101
+ this.shadow.innerHTML = `
102
+ ${iconMarkup}
103
+ <span part="label"><slot></slot></span>
104
+ ${this.removable ? `<button type="button" class="viking-badge-remove" part="remove" aria-label="Remove">${renderInlineIcon("x", 14)}</button>` : ""}
105
+ `;
106
+
107
+ this.shadow
108
+ .querySelector(".viking-badge-remove")
109
+ ?.addEventListener("click", this.onRemove);
110
+ }
111
+ }
112
+
113
+ export const registerVikingBadgeWc = (): void => {
114
+ defineCustomElement(VikingBadgeWc.tag, VikingBadgeWc);
115
+ defineCustomElementAlias(VikingBadgeWc.legacyTag, VikingBadgeWc);
116
+ };
@@ -0,0 +1,165 @@
1
+ import { attachShadowStyles, readBoolAttr } from "../core/base";
2
+ import {
3
+ defineCustomElement,
4
+ defineCustomElementAlias,
5
+ escapeHtml,
6
+ HTMLElementBase,
7
+ } from "../core/dom";
8
+ import { VIKING_BUTTON_STYLES } from "../core/styles";
9
+
10
+ const VARIANTS = new Set([
11
+ "outline",
12
+ "primary",
13
+ "secondary",
14
+ "filled",
15
+ "danger",
16
+ "ghost",
17
+ "subtle",
18
+ ]);
19
+
20
+ const SIZES = new Set(["sm", "xs"]);
21
+
22
+ /**
23
+ * Framework-agnostic Viking button Web Component.
24
+ * Tag: `viking-button` (legacy alias: `viking-button-wc`)
25
+ *
26
+ * @example
27
+ * <viking-button variant="primary">Launch</viking-button>
28
+ */
29
+ export class VikingButtonWc extends HTMLElementBase {
30
+ static readonly tag = "viking-button";
31
+ static readonly legacyTag = "viking-button-wc";
32
+
33
+ static get observedAttributes(): string[] {
34
+ return [
35
+ "variant",
36
+ "size",
37
+ "type",
38
+ "disabled",
39
+ "loading",
40
+ "href",
41
+ "target",
42
+ "aria-label",
43
+ "aria-busy",
44
+ "square",
45
+ "full-width",
46
+ "compact",
47
+ ];
48
+ }
49
+
50
+ private readonly shadow: ShadowRoot;
51
+ private control: HTMLButtonElement | HTMLAnchorElement | null = null;
52
+
53
+ constructor() {
54
+ super();
55
+ this.shadow = this.attachShadow({ mode: "open" });
56
+ attachShadowStyles(this.shadow, VIKING_BUTTON_STYLES);
57
+ }
58
+
59
+ connectedCallback(): void {
60
+ this.render();
61
+ this.syncHostSemantics();
62
+ this.control?.addEventListener("click", this.onClick);
63
+ }
64
+
65
+ disconnectedCallback(): void {
66
+ this.control?.removeEventListener("click", this.onClick);
67
+ }
68
+
69
+ attributeChangedCallback(): void {
70
+ if (this.isConnected) {
71
+ this.render();
72
+ this.syncHostSemantics();
73
+ }
74
+ }
75
+
76
+ private readonly onClick = (event: Event): void => {
77
+ if (this.disabled || this.loading) {
78
+ event.preventDefault();
79
+ event.stopPropagation();
80
+ return;
81
+ }
82
+ this.dispatchEvent(
83
+ new CustomEvent("viking-press", {
84
+ bubbles: true,
85
+ composed: true,
86
+ detail: event,
87
+ }),
88
+ );
89
+ };
90
+
91
+ private get variant(): string {
92
+ const value = this.getAttribute("variant") ?? "outline";
93
+ return VARIANTS.has(value) ? value : "outline";
94
+ }
95
+
96
+ private get size(): string | null {
97
+ const value = this.getAttribute("size");
98
+ return value && SIZES.has(value) ? value : null;
99
+ }
100
+
101
+ private get disabled(): boolean {
102
+ return readBoolAttr(this, "disabled");
103
+ }
104
+
105
+ private get loading(): boolean {
106
+ return readBoolAttr(this, "loading");
107
+ }
108
+
109
+ private get square(): boolean {
110
+ return readBoolAttr(this, "square");
111
+ }
112
+
113
+ private syncHostSemantics(): void {
114
+ if (!this.hasAttribute("role")) {
115
+ this.setAttribute("role", this.getAttribute("href") ? "link" : "button");
116
+ }
117
+ }
118
+
119
+ private render(): void {
120
+ const href = this.getAttribute("href");
121
+ const isLink = Boolean(href);
122
+ const tag = isLink ? "a" : "button";
123
+
124
+ const classes = [
125
+ "viking-btn",
126
+ `viking-btn-${this.variant}`,
127
+ this.size ? `viking-btn-${this.size}` : "",
128
+ this.square ? "viking-btn-square" : "",
129
+ ]
130
+ .filter(Boolean)
131
+ .join(" ");
132
+
133
+ const label = this.getAttribute("aria-label") ?? "";
134
+ const busy =
135
+ this.getAttribute("aria-busy") === "true" || this.loading ? "true" : null;
136
+ const type = escapeHtml(this.getAttribute("type") ?? "button");
137
+ const hrefValue = href ? escapeHtml(href) : "";
138
+ const target = this.getAttribute("target");
139
+ const safeTarget = target ? escapeHtml(target) : "";
140
+
141
+ this.shadow.innerHTML = `
142
+ <${tag}
143
+ class="${classes}"
144
+ part="control"
145
+ ${isLink ? `href="${hrefValue}"` : `type="${type}"`}
146
+ ${isLink && safeTarget ? `target="${safeTarget}"` : ""}
147
+ ${isLink && target === "_blank" ? 'rel="noopener noreferrer"' : ""}
148
+ ${this.disabled || this.loading ? "disabled" : ""}
149
+ ${label ? `aria-label="${escapeHtml(label)}"` : ""}
150
+ ${busy ? `aria-busy="${busy}"` : ""}
151
+ ${this.disabled && isLink ? 'aria-disabled="true" tabindex="-1"' : ""}
152
+ >
153
+ ${this.loading ? '<span class="viking-btn-spinner" aria-hidden="true"></span>' : ""}
154
+ <span class="viking-btn-label" part="label"><slot></slot></span>
155
+ </${tag}>
156
+ `;
157
+
158
+ this.control = this.shadow.querySelector(tag);
159
+ }
160
+ }
161
+
162
+ export const registerVikingButtonWc = (): void => {
163
+ defineCustomElement(VikingButtonWc.tag, VikingButtonWc);
164
+ defineCustomElementAlias(VikingButtonWc.legacyTag, VikingButtonWc);
165
+ };
@@ -0,0 +1,105 @@
1
+ import { attachShadowStyles } from "../core/base";
2
+ import {
3
+ defineCustomElement,
4
+ defineCustomElementAlias,
5
+ escapeHtml,
6
+ HTMLElementBase,
7
+ } from "../core/dom";
8
+ import { renderInlineIcon, TONE_ICON_NAMES } from "../core/icons-inline";
9
+ import { VIKING_CALLOUT_STYLES } from "../core/styles";
10
+ import type { VikingWcTone } from "../core/types";
11
+
12
+ const TONES = new Set<VikingWcTone>([
13
+ "accent",
14
+ "secondary",
15
+ "success",
16
+ "warning",
17
+ "danger",
18
+ "info",
19
+ "muted",
20
+ ]);
21
+
22
+ /**
23
+ * Framework-agnostic Viking callout Web Component.
24
+ * Tag: `viking-callout` (legacy alias: `viking-callout-wc`)
25
+ *
26
+ * @attr tone - Semantic tone (default: info)
27
+ * @attr heading - Bold callout title
28
+ * @attr icon - Override leading icon from the Viking registry
29
+ * @attr dismissible - Shows dismiss control; dispatches `viking-close`
30
+ *
31
+ * @example
32
+ * <viking-callout-wc tone="warning" heading="Degraded worker">Lag exceeds 3s.</viking-callout-wc>
33
+ */
34
+ export class VikingCalloutWc extends HTMLElementBase {
35
+ static readonly tag = "viking-callout";
36
+ static readonly legacyTag = "viking-callout-wc";
37
+
38
+ static get observedAttributes(): string[] {
39
+ return ["tone", "heading", "icon", "dismissible", "hidden"];
40
+ }
41
+
42
+ private readonly shadow: ShadowRoot;
43
+
44
+ constructor() {
45
+ super();
46
+ this.shadow = this.attachShadow({ mode: "open" });
47
+ attachShadowStyles(this.shadow, VIKING_CALLOUT_STYLES);
48
+ }
49
+
50
+ connectedCallback(): void {
51
+ this.render();
52
+ }
53
+
54
+ attributeChangedCallback(): void {
55
+ if (this.isConnected) {
56
+ this.render();
57
+ }
58
+ }
59
+
60
+ private get tone(): VikingWcTone {
61
+ const value = (this.getAttribute("tone") ?? "info") as VikingWcTone;
62
+ return TONES.has(value) ? value : "info";
63
+ }
64
+
65
+ private get dismissible(): boolean {
66
+ return (
67
+ this.hasAttribute("dismissible") &&
68
+ this.getAttribute("dismissible") !== "false"
69
+ );
70
+ }
71
+
72
+ private readonly onDismiss = (): void => {
73
+ this.setAttribute("hidden", "");
74
+ this.dispatchEvent(
75
+ new CustomEvent("viking-close", { bubbles: true, composed: true }),
76
+ );
77
+ };
78
+
79
+ private render(): void {
80
+ const heading = this.getAttribute("heading") ?? "";
81
+ const iconName =
82
+ this.getAttribute("icon") ?? TONE_ICON_NAMES[this.tone] ?? "info";
83
+ const iconMarkup = renderInlineIcon(iconName, 22, "viking-callout-icon");
84
+
85
+ this.shadow.innerHTML = `
86
+ <div class="viking-callout viking-callout-${this.tone}" role="note" part="surface">
87
+ <span part="icon">${iconMarkup}</span>
88
+ <div class="viking-callout-body" part="body">
89
+ ${heading ? `<p class="viking-callout-heading" part="heading">${escapeHtml(heading)}</p>` : ""}
90
+ <div class="viking-callout-text" part="text"><slot></slot></div>
91
+ </div>
92
+ ${this.dismissible ? `<button type="button" class="viking-callout-close" part="close" aria-label="Dismiss">${renderInlineIcon("x", 18)}</button>` : ""}
93
+ </div>
94
+ `;
95
+
96
+ this.shadow
97
+ .querySelector(".viking-callout-close")
98
+ ?.addEventListener("click", this.onDismiss);
99
+ }
100
+ }
101
+
102
+ export const registerVikingCalloutWc = (): void => {
103
+ defineCustomElement(VikingCalloutWc.tag, VikingCalloutWc);
104
+ defineCustomElementAlias(VikingCalloutWc.legacyTag, VikingCalloutWc);
105
+ };
@@ -0,0 +1,66 @@
1
+ import {
2
+ defineCustomElement,
3
+ defineCustomElementAlias,
4
+ HTMLElementBase,
5
+ } from "../core/dom";
6
+
7
+ /**
8
+ * Framework-agnostic Viking card Web Component (light DOM — inherits global viking-card CSS).
9
+ * Tag: `viking-card` (legacy alias: `viking-card-wc`)
10
+ *
11
+ * Uses light DOM so slotted content inherits `viking-card`, `viking-card-header`, and related
12
+ * classes from the synced static CSS bundle.
13
+ *
14
+ * @attr compact - Applies `viking-card-compact` padding
15
+ * @attr interactive - Hover lift for clickable cards
16
+ * @attr title - Accessible name when the card is a landmark region
17
+ *
18
+ * @example
19
+ * <viking-card-wc compact title="Event throughput">
20
+ * <div class="viking-card-header">
21
+ * <h3 class="viking-heading viking-heading-sm">Event throughput</h3>
22
+ * </div>
23
+ * <p class="viking-text-muted">8.2K events/sec</p>
24
+ * </viking-card-wc>
25
+ */
26
+ export class VikingCardWc extends HTMLElementBase {
27
+ static readonly tag = "viking-card";
28
+ static readonly legacyTag = "viking-card-wc";
29
+
30
+ static get observedAttributes(): string[] {
31
+ return ["compact", "interactive", "title"];
32
+ }
33
+
34
+ connectedCallback(): void {
35
+ this.syncClasses();
36
+ }
37
+
38
+ attributeChangedCallback(): void {
39
+ if (this.isConnected) {
40
+ this.syncClasses();
41
+ }
42
+ }
43
+
44
+ private syncClasses(): void {
45
+ this.classList.add("viking-card");
46
+ this.classList.toggle("viking-card-compact", this.hasAttribute("compact"));
47
+ this.classList.toggle(
48
+ "viking-card-interactive",
49
+ this.hasAttribute("interactive"),
50
+ );
51
+
52
+ const title = this.getAttribute("title");
53
+ if (title) {
54
+ this.setAttribute("role", "region");
55
+ this.setAttribute("aria-label", title);
56
+ } else {
57
+ this.removeAttribute("role");
58
+ this.removeAttribute("aria-label");
59
+ }
60
+ }
61
+ }
62
+
63
+ export const registerVikingCardWc = (): void => {
64
+ defineCustomElement(VikingCardWc.tag, VikingCardWc);
65
+ defineCustomElementAlias(VikingCardWc.legacyTag, VikingCardWc);
66
+ };
@@ -0,0 +1,56 @@
1
+ /** Attaches constructable stylesheet to a shadow root (with fallback). */
2
+ export const attachShadowStyles = (shadow: ShadowRoot, css: string): void => {
3
+ if (
4
+ "adoptedStyleSheets" in Document.prototype &&
5
+ "replaceSync" in CSSStyleSheet.prototype
6
+ ) {
7
+ const sheet = new CSSStyleSheet();
8
+ sheet.replaceSync(css);
9
+ shadow.adoptedStyleSheets = [sheet];
10
+ return;
11
+ }
12
+
13
+ const style = document.createElement("style");
14
+ style.textContent = css;
15
+ shadow.append(style);
16
+ };
17
+
18
+ /** Reads a boolean attribute presence/value. */
19
+ export const readBoolAttr = (el: HTMLElement, name: string): boolean =>
20
+ el.hasAttribute(name) && el.getAttribute(name) !== "false";
21
+
22
+ /** Safely sets form value when ElementInternals is fully supported (e.g. not in jsdom). */
23
+ export const setFormValue = (
24
+ internals: ElementInternals | null,
25
+ value: string,
26
+ ): void => {
27
+ if (internals && typeof internals.setFormValue === "function") {
28
+ internals.setFormValue(value);
29
+ }
30
+ };
31
+
32
+ /** Attaches ElementInternals where available without breaking older WebKit or test DOMs. */
33
+ export const attachElementInternals = (
34
+ el: HTMLElement,
35
+ ): ElementInternals | null => {
36
+ const attachInternals = (
37
+ el as HTMLElement & { attachInternals?: () => ElementInternals }
38
+ ).attachInternals;
39
+ return typeof attachInternals === "function"
40
+ ? attachInternals.call(el)
41
+ : null;
42
+ };
43
+
44
+ /** Opens a native dialog when showModal is available. */
45
+ export const showModalDialog = (dialog: HTMLDialogElement | null): void => {
46
+ if (dialog && typeof dialog.showModal === "function" && !dialog.open) {
47
+ dialog.showModal();
48
+ }
49
+ };
50
+
51
+ /** Closes a native dialog when close is available. */
52
+ export const closeModalDialog = (dialog: HTMLDialogElement | null): void => {
53
+ if (dialog && typeof dialog.close === "function" && dialog.open) {
54
+ dialog.close();
55
+ }
56
+ };
@@ -0,0 +1,53 @@
1
+ export const HTMLElementBase: typeof HTMLElement =
2
+ typeof HTMLElement === "undefined"
3
+ ? (class {} as unknown as typeof HTMLElement)
4
+ : HTMLElement;
5
+
6
+ /** Stable unique id for associating labels and controls. */
7
+ export const vikingWcUid = (prefix: string): string =>
8
+ `${prefix}-${Math.random().toString(36).slice(2, 9)}`;
9
+
10
+ /** Escape text for safe innerHTML insertion. */
11
+ export const escapeHtml = (value: string): string => {
12
+ const entities: Record<string, string> = {
13
+ "&": "&amp;",
14
+ "<": "&lt;",
15
+ ">": "&gt;",
16
+ '"': "&quot;",
17
+ "'": "&#39;",
18
+ };
19
+
20
+ return value.replace(
21
+ /[&<>"']/g,
22
+ (character) => entities[character] ?? character,
23
+ );
24
+ };
25
+
26
+ /** Platform modifier label for keyboard shortcut hints. */
27
+ export const modKeyLabel = (): string =>
28
+ typeof navigator !== "undefined" &&
29
+ /Mac|iPhone|iPad/i.test(navigator.platform)
30
+ ? "⌘"
31
+ : "Ctrl";
32
+
33
+ /** Register a custom element when the runtime supports it (skip during SSR/prerender). */
34
+ export const defineCustomElement = (
35
+ tag: string,
36
+ ctor: CustomElementConstructor,
37
+ ): void => {
38
+ if (typeof customElements === "undefined" || customElements.get(tag)) {
39
+ return;
40
+ }
41
+ customElements.define(tag, ctor);
42
+ };
43
+
44
+ /** Register an alias tag with a fresh subclass; customElements disallows reusing constructors. */
45
+ export const defineCustomElementAlias = <T extends CustomElementConstructor>(
46
+ tag: string,
47
+ ctor: T,
48
+ ): void => {
49
+ if (typeof customElements === "undefined" || customElements.get(tag)) {
50
+ return;
51
+ }
52
+ customElements.define(tag, class extends ctor {});
53
+ };
@@ -0,0 +1,44 @@
1
+ import {
2
+ resolveVikingIcon,
3
+ VIKING_FILLED_ICON_NAMES,
4
+ VIKING_ICON_FILLED_PATHS,
5
+ VIKING_ICON_PATHS,
6
+ type VikingIconName,
7
+ } from "../../lib/core/icons";
8
+
9
+ const FILLED_ICON_SET = new Set<string>(VIKING_FILLED_ICON_NAMES);
10
+
11
+ /**
12
+ * Renders a zero-dependency inline SVG icon for Web Components.
13
+ * Uses the same registry as `viking-icon` in Angular.
14
+ */
15
+ export const renderInlineIcon = (
16
+ name: string,
17
+ size = 16,
18
+ className = "viking-wc-icon",
19
+ ): string => {
20
+ const resolved = resolveVikingIcon(name);
21
+ const filled = FILLED_ICON_SET.has(resolved);
22
+ const paths = filled
23
+ ? (VIKING_ICON_FILLED_PATHS[resolved as VikingIconName] ??
24
+ VIKING_ICON_PATHS[resolved])
25
+ : VIKING_ICON_PATHS[resolved];
26
+
27
+ if (filled) {
28
+ return `<svg class="${className}" width="${size}" height="${size}" viewBox="0 0 24 24" fill="currentColor" fill-rule="evenodd" aria-hidden="true">${paths}</svg>`;
29
+ }
30
+
31
+ return `<svg class="${className}" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths}</svg>`;
32
+ };
33
+
34
+ /** Default tone → icon mapping shared by callout and badge surfaces. */
35
+ export const TONE_ICON_NAMES: Record<string, string> = {
36
+ accent: "info",
37
+ secondary: "info",
38
+ success: "check-circle",
39
+ warning: "alert-triangle",
40
+ danger: "alert-circle",
41
+ info: "info",
42
+ muted: "info",
43
+ subtle: "info",
44
+ };