@dataengineeringformachinelearning/viking-ui 1.0.2 → 3.0.0-alpha.2
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 +180 -41
- package/dist/deml-components.css +1122 -0
- package/dist/design-tokens.css +688 -0
- package/dist/elements/button/viking-button.d.ts +14 -0
- package/dist/elements/card/viking-card.d.ts +8 -0
- package/dist/elements/core/dom.d.ts +4 -0
- package/dist/elements/core/styles.d.ts +2 -0
- package/dist/elements/modal/viking-modal.d.ts +15 -0
- package/dist/fonts/inter/InterVariable-Italic.woff2 +0 -0
- package/dist/fonts/inter/InterVariable.woff2 +0 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4051 -0
- package/dist/lib/core/brand-icons.d.ts +29 -0
- package/dist/lib/core/icons.d.ts +132 -0
- package/dist/lib/core/integration-brand-icons.d.ts +18 -0
- package/dist/lib/core/lucide-paths.generated.d.ts +90 -0
- package/dist/lib/site-drakkar/site-drakkar.config.d.ts +50 -0
- package/dist/lib/site-drakkar/suite-search-items.d.ts +17 -0
- package/dist/viking-components.css +1837 -0
- package/dist/viking-tokens.json +318 -0
- package/dist/viking-ui-elements.js +1750 -0
- package/dist/viking-ui.css +1 -0
- package/dist/web/badge/viking-badge-wc.d.ts +28 -0
- package/dist/web/button/viking-button-wc.d.ts +28 -0
- package/dist/web/callout/viking-callout-wc.d.ts +27 -0
- package/dist/web/card/viking-card-wc.d.ts +29 -0
- package/dist/web/core/base.d.ts +12 -0
- package/dist/web/core/dom.d.ts +11 -0
- package/dist/web/core/icons-inline.d.ts +7 -0
- package/dist/web/core/styles.d.ts +12 -0
- package/dist/web/core/types.d.ts +10 -0
- package/dist/web/field/viking-field-wc.d.ts +32 -0
- package/dist/web/index.d.ts +15 -0
- package/dist/web/input/viking-input-wc.d.ts +32 -0
- package/dist/web/modal/viking-modal-wc.d.ts +50 -0
- package/dist/web/search-palette/viking-search-palette-wc.d.ts +64 -0
- package/dist/web/select/viking-select-wc.d.ts +48 -0
- package/dist/web/suite-header/viking-suite-header-wc.d.ts +36 -0
- package/dist/web/suite-search-palette/viking-suite-search-palette-wc.d.ts +39 -0
- package/dist/web/theme-toggle/viking-theme-toggle-wc.d.ts +19 -0
- package/dist/web/types.d.ts +40 -0
- package/dist/web-components/index.d.ts +1 -0
- package/dist/web-components.js +1750 -0
- package/dist/widget.js +1182 -0
- package/package.json +89 -30
- package/src/assets/fonts/inter/InterVariable-Italic.woff2 +0 -0
- package/src/assets/fonts/inter/InterVariable.woff2 +0 -0
- package/src/elements/button/viking-button.ts +201 -0
- package/src/elements/card/viking-card.ts +60 -0
- package/src/elements/core/dom.ts +33 -0
- package/src/elements/core/styles.ts +28 -0
- package/src/elements/modal/viking-modal.ts +154 -0
- package/src/index.ts +1 -0
- package/src/lib/core/brand-icons.ts +62 -0
- package/src/lib/core/icons.ts +206 -0
- package/src/lib/core/integration-brand-icons.ts +77 -0
- package/src/lib/core/lucide-paths.generated.ts +130 -0
- package/src/lib/site-drakkar/site-drakkar.config.ts +295 -0
- package/src/lib/site-drakkar/suite-search-items.ts +246 -0
- package/src/styles/_buttons.scss +301 -0
- package/src/styles/_component-primitives.scss +183 -0
- package/src/styles/_drakkar-buttons.scss +85 -0
- package/src/styles/_fonts.scss +22 -0
- package/src/styles/_forms.scss +66 -0
- package/src/styles/_input.scss +106 -0
- package/src/styles/_layout-enforcement.scss +166 -0
- package/src/styles/_layout-rhythm.scss +158 -0
- package/src/styles/_legacy-aliases.scss +212 -0
- package/src/styles/_series-colors.scss +24 -0
- package/src/styles/_static-primitives.scss +857 -0
- package/src/styles/_typography.scss +87 -0
- package/src/styles/_variables.scss +527 -0
- package/src/styles/components/a11y.scss +90 -0
- package/src/styles/components/badges.scss +102 -0
- package/src/styles/components/extracted-utilities.scss +459 -0
- package/src/styles/components/footer.scss +155 -0
- package/src/styles/components/icon-heading.scss +47 -0
- package/src/styles/components/icon-inline.scss +78 -0
- package/src/styles/components/layout.scss +59 -0
- package/src/styles/components/utilities.scss +131 -0
- package/src/styles/components/viking-inspired.scss +204 -0
- package/src/styles/components/whitepaper-cta.scss +130 -0
- package/src/styles/components-bundle.scss +9 -0
- package/src/styles/components.scss +175 -0
- package/src/styles/deml-components.scss +10 -0
- package/src/styles/index.scss +8 -0
- package/src/styles/layout-shell.scss +39 -11
- package/src/styles/page-shell.scss +663 -33
- package/src/styles/static-navbar.scss +862 -146
- package/src/styles/surfaces/backend.scss +142 -0
- package/src/styles/surfaces/docs-global.scss +73 -0
- package/src/styles/surfaces/docs-shell.scss +412 -0
- package/src/styles/surfaces/docs-showcase.scss +1051 -0
- package/src/styles/surfaces/marketing-cta.scss +62 -0
- package/src/styles/surfaces/marketing-docs-bento.scss +67 -0
- package/src/styles/surfaces/marketing-global.scss +748 -0
- package/src/styles/surfaces/marketing-landing.scss +1260 -0
- package/src/styles/surfaces/marketing-publication.scss +535 -0
- package/src/styles/surfaces/marketing-theme.scss +14 -0
- package/src/styles/surfaces/swagger-ui.scss +921 -0
- package/src/styles/tokens-export.scss +18 -0
- package/src/styles/tokens.scss +5 -0
- package/src/styles/viking-ui-bundle.scss +20 -2
- package/src/styles/viking-ui.scss +213 -51
- package/src/tokens/viking-tokens.json +318 -0
- package/src/web/badge/viking-badge-wc.ts +116 -0
- package/src/web/button/viking-button-wc.ts +165 -0
- package/src/web/callout/viking-callout-wc.ts +105 -0
- package/src/web/card/viking-card-wc.ts +66 -0
- package/src/web/core/base.ts +56 -0
- package/src/web/core/dom.ts +53 -0
- package/src/web/core/icons-inline.ts +44 -0
- package/src/web/core/styles.ts +1138 -0
- package/src/web/core/types.ts +19 -0
- package/src/web/field/viking-field-wc.ts +154 -0
- package/src/web/index.ts +73 -0
- package/src/web/input/viking-input-wc.ts +211 -0
- package/src/web/modal/viking-modal-wc.ts +187 -0
- package/src/web/search-palette/viking-search-palette-wc.ts +494 -0
- package/src/web/select/viking-select-wc.ts +218 -0
- package/src/web/suite-header/viking-suite-header-wc.ts +714 -0
- package/src/web/suite-search-palette/viking-suite-search-palette-wc.ts +234 -0
- package/src/web/theme-toggle/viking-theme-toggle-wc.ts +139 -0
- package/src/web/types.ts +37 -0
- package/src/web-components/index.ts +1 -0
- package/fesm2022/dataengineeringformachinelearning-viking-ui.mjs +0 -6114
- package/fesm2022/dataengineeringformachinelearning-viking-ui.mjs.map +0 -1
- package/types/dataengineeringformachinelearning-viking-ui.d.ts +0 -1397
- package/viking.manifest.json +0 -238
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Shared Web Component item types. */
|
|
2
|
+
export type VikingSearchPaletteItem = {
|
|
3
|
+
title: string;
|
|
4
|
+
href: string;
|
|
5
|
+
snippet?: string;
|
|
6
|
+
group?: string;
|
|
7
|
+
keywords?: string[];
|
|
8
|
+
action?: "cookie-settings" | "bug-report";
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type VikingWcTone =
|
|
12
|
+
| "accent"
|
|
13
|
+
| "secondary"
|
|
14
|
+
| "success"
|
|
15
|
+
| "warning"
|
|
16
|
+
| "danger"
|
|
17
|
+
| "info"
|
|
18
|
+
| "muted"
|
|
19
|
+
| "subtle";
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { attachShadowStyles, readBoolAttr } from "../core/base";
|
|
2
|
+
import {
|
|
3
|
+
defineCustomElement,
|
|
4
|
+
defineCustomElementAlias,
|
|
5
|
+
escapeHtml,
|
|
6
|
+
HTMLElementBase,
|
|
7
|
+
vikingWcUid,
|
|
8
|
+
} from "../core/dom";
|
|
9
|
+
import { VIKING_FIELD_STYLES } from "../core/styles";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Framework-agnostic Viking field stack Web Component.
|
|
13
|
+
* Tag: `viking-field` (legacy alias: `viking-field-wc`)
|
|
14
|
+
*
|
|
15
|
+
* @attr label - Visible field label
|
|
16
|
+
* @attr description - Helper text below the control
|
|
17
|
+
* @attr error - Validation message; sets aria-invalid on the slotted control
|
|
18
|
+
* @attr required - Adds a non-color-only required marker
|
|
19
|
+
* @attr width - full | half
|
|
20
|
+
*
|
|
21
|
+
* @slot default - Form control, usually `viking-input`, `viking-select`, or native input/select
|
|
22
|
+
*/
|
|
23
|
+
export class VikingFieldWc extends HTMLElementBase {
|
|
24
|
+
static readonly tag = "viking-field";
|
|
25
|
+
static readonly legacyTag = "viking-field-wc";
|
|
26
|
+
|
|
27
|
+
static get observedAttributes(): string[] {
|
|
28
|
+
return ["label", "description", "error", "required", "width"];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private readonly shadow: ShadowRoot;
|
|
32
|
+
private readonly labelId = vikingWcUid("viking-field-label");
|
|
33
|
+
private readonly descriptionId = vikingWcUid("viking-field-description");
|
|
34
|
+
private readonly errorId = vikingWcUid("viking-field-error");
|
|
35
|
+
private slotEl: HTMLSlotElement | null = null;
|
|
36
|
+
|
|
37
|
+
constructor() {
|
|
38
|
+
super();
|
|
39
|
+
this.shadow = this.attachShadow({ mode: "open" });
|
|
40
|
+
attachShadowStyles(this.shadow, VIKING_FIELD_STYLES);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
connectedCallback(): void {
|
|
44
|
+
this.render();
|
|
45
|
+
this.syncControlA11y();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
disconnectedCallback(): void {
|
|
49
|
+
this.slotEl?.removeEventListener("slotchange", this.syncControlA11y);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
attributeChangedCallback(): void {
|
|
53
|
+
if (this.isConnected) {
|
|
54
|
+
this.render();
|
|
55
|
+
this.syncControlA11y();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private get control(): HTMLElement | null {
|
|
60
|
+
const assigned = this.slotEl?.assignedElements({ flatten: true }) ?? [];
|
|
61
|
+
return (
|
|
62
|
+
assigned.find(
|
|
63
|
+
(node): node is HTMLElement => node instanceof HTMLElement,
|
|
64
|
+
) ?? null
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private readonly focusControl = (): void => {
|
|
69
|
+
const control = this.control as HTMLElement & { focus?: () => void };
|
|
70
|
+
control?.focus?.();
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
private readonly syncControlA11y = (): void => {
|
|
74
|
+
const control = this.control;
|
|
75
|
+
if (!control) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const description = [
|
|
80
|
+
this.getAttribute("description") ?? "",
|
|
81
|
+
this.getAttribute("error") ?? "",
|
|
82
|
+
]
|
|
83
|
+
.filter(Boolean)
|
|
84
|
+
.join(" ");
|
|
85
|
+
|
|
86
|
+
const label = this.getAttribute("label") ?? "";
|
|
87
|
+
if (label && !control.hasAttribute("aria-label")) {
|
|
88
|
+
control.setAttribute("aria-label", label);
|
|
89
|
+
}
|
|
90
|
+
if (description) {
|
|
91
|
+
control.setAttribute("aria-description", description);
|
|
92
|
+
} else {
|
|
93
|
+
control.removeAttribute("aria-description");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (this.getAttribute("error")) {
|
|
97
|
+
control.setAttribute("aria-invalid", "true");
|
|
98
|
+
control.setAttribute("error", this.getAttribute("error") ?? "");
|
|
99
|
+
} else {
|
|
100
|
+
control.removeAttribute("aria-invalid");
|
|
101
|
+
if (control.getAttribute("error") === "") {
|
|
102
|
+
control.removeAttribute("error");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (readBoolAttr(this, "required")) {
|
|
107
|
+
control.setAttribute("required", "");
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
private render(): void {
|
|
112
|
+
const label = this.getAttribute("label") ?? "";
|
|
113
|
+
const description = this.getAttribute("description") ?? "";
|
|
114
|
+
const error = this.getAttribute("error") ?? "";
|
|
115
|
+
const required = readBoolAttr(this, "required");
|
|
116
|
+
|
|
117
|
+
this.shadow.innerHTML = `
|
|
118
|
+
<div class="viking-field" part="field" role="group" aria-labelledby="${this.labelId}">
|
|
119
|
+
${
|
|
120
|
+
label
|
|
121
|
+
? `<div class="viking-field-label-row" part="label-row">
|
|
122
|
+
<span class="viking-field-label" part="label" id="${this.labelId}">
|
|
123
|
+
<span>${escapeHtml(label)}</span>
|
|
124
|
+
${required ? `<span class="viking-field-required" aria-label="required">*</span>` : ""}
|
|
125
|
+
</span>
|
|
126
|
+
</div>`
|
|
127
|
+
: `<span id="${this.labelId}" hidden>Form field</span>`
|
|
128
|
+
}
|
|
129
|
+
<div class="viking-field-control" part="control"><slot></slot></div>
|
|
130
|
+
${
|
|
131
|
+
description
|
|
132
|
+
? `<p id="${this.descriptionId}" class="viking-field-description" part="description">${escapeHtml(description)}</p>`
|
|
133
|
+
: ""
|
|
134
|
+
}
|
|
135
|
+
${
|
|
136
|
+
error
|
|
137
|
+
? `<p id="${this.errorId}" class="viking-field-error" part="error" role="alert">${escapeHtml(error)}</p>`
|
|
138
|
+
: ""
|
|
139
|
+
}
|
|
140
|
+
</div>
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
this.slotEl = this.shadow.querySelector("slot");
|
|
144
|
+
this.slotEl?.addEventListener("slotchange", this.syncControlA11y);
|
|
145
|
+
this.shadow
|
|
146
|
+
.querySelector(".viking-field-label")
|
|
147
|
+
?.addEventListener("click", this.focusControl);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export const registerVikingFieldWc = (): void => {
|
|
152
|
+
defineCustomElement(VikingFieldWc.tag, VikingFieldWc);
|
|
153
|
+
defineCustomElementAlias(VikingFieldWc.legacyTag, VikingFieldWc);
|
|
154
|
+
};
|
package/src/web/index.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { registerVikingBadgeWc } from "./badge/viking-badge-wc";
|
|
2
|
+
import { registerVikingButtonWc } from "./button/viking-button-wc";
|
|
3
|
+
import { registerVikingCalloutWc } from "./callout/viking-callout-wc";
|
|
4
|
+
import { registerVikingCardWc } from "./card/viking-card-wc";
|
|
5
|
+
import { registerVikingFieldWc } from "./field/viking-field-wc";
|
|
6
|
+
import { registerVikingInputWc } from "./input/viking-input-wc";
|
|
7
|
+
import { registerVikingModalWc } from "./modal/viking-modal-wc";
|
|
8
|
+
import { registerVikingSearchPaletteWc } from "./search-palette/viking-search-palette-wc";
|
|
9
|
+
import { registerVikingSuiteHeaderWc } from "./suite-header/viking-suite-header-wc";
|
|
10
|
+
import { registerVikingSuiteSearchPaletteWc } from "./suite-search-palette/viking-suite-search-palette-wc";
|
|
11
|
+
import { registerVikingSelectWc } from "./select/viking-select-wc";
|
|
12
|
+
import { registerVikingThemeToggleWc } from "./theme-toggle/viking-theme-toggle-wc";
|
|
13
|
+
|
|
14
|
+
export { VikingBadgeWc, registerVikingBadgeWc } from "./badge/viking-badge-wc";
|
|
15
|
+
export {
|
|
16
|
+
VikingButtonWc,
|
|
17
|
+
registerVikingButtonWc,
|
|
18
|
+
} from "./button/viking-button-wc";
|
|
19
|
+
export {
|
|
20
|
+
VikingCalloutWc,
|
|
21
|
+
registerVikingCalloutWc,
|
|
22
|
+
} from "./callout/viking-callout-wc";
|
|
23
|
+
export { VikingCardWc, registerVikingCardWc } from "./card/viking-card-wc";
|
|
24
|
+
export { VikingFieldWc, registerVikingFieldWc } from "./field/viking-field-wc";
|
|
25
|
+
export { VikingInputWc, registerVikingInputWc } from "./input/viking-input-wc";
|
|
26
|
+
export { VikingModalWc, registerVikingModalWc } from "./modal/viking-modal-wc";
|
|
27
|
+
export {
|
|
28
|
+
VikingSearchPaletteWc,
|
|
29
|
+
registerVikingSearchPaletteWc,
|
|
30
|
+
type VikingSearchPaletteItem,
|
|
31
|
+
} from "./search-palette/viking-search-palette-wc";
|
|
32
|
+
export {
|
|
33
|
+
VikingSuiteSearchPaletteWc,
|
|
34
|
+
registerVikingSuiteSearchPaletteWc,
|
|
35
|
+
} from "./suite-search-palette/viking-suite-search-palette-wc";
|
|
36
|
+
export {
|
|
37
|
+
VikingSuiteHeaderWc,
|
|
38
|
+
registerVikingSuiteHeaderWc,
|
|
39
|
+
} from "./suite-header/viking-suite-header-wc";
|
|
40
|
+
export {
|
|
41
|
+
VikingSelectWc,
|
|
42
|
+
registerVikingSelectWc,
|
|
43
|
+
} from "./select/viking-select-wc";
|
|
44
|
+
export {
|
|
45
|
+
VikingThemeToggleWc,
|
|
46
|
+
registerVikingThemeToggleWc,
|
|
47
|
+
} from "./theme-toggle/viking-theme-toggle-wc";
|
|
48
|
+
export type { VikingWcTone } from "./core/types";
|
|
49
|
+
|
|
50
|
+
/** Register all Viking-UI Web Components. Safe to call multiple times. */
|
|
51
|
+
export const registerVikingElements = (): void => {
|
|
52
|
+
registerVikingButtonWc();
|
|
53
|
+
registerVikingFieldWc();
|
|
54
|
+
registerVikingInputWc();
|
|
55
|
+
registerVikingBadgeWc();
|
|
56
|
+
registerVikingCalloutWc();
|
|
57
|
+
registerVikingCardWc();
|
|
58
|
+
registerVikingSelectWc();
|
|
59
|
+
registerVikingModalWc();
|
|
60
|
+
registerVikingSearchPaletteWc();
|
|
61
|
+
registerVikingSuiteHeaderWc();
|
|
62
|
+
registerVikingSuiteSearchPaletteWc();
|
|
63
|
+
registerVikingThemeToggleWc();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Auto-register when loaded as a script bundle (Astro, Django, static HTML).
|
|
67
|
+
if (typeof globalThis !== "undefined" && typeof document !== "undefined") {
|
|
68
|
+
if (document.readyState === "loading") {
|
|
69
|
+
document.addEventListener("DOMContentLoaded", registerVikingElements);
|
|
70
|
+
} else {
|
|
71
|
+
registerVikingElements();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attachElementInternals,
|
|
3
|
+
attachShadowStyles,
|
|
4
|
+
readBoolAttr,
|
|
5
|
+
setFormValue,
|
|
6
|
+
} from "../core/base";
|
|
7
|
+
import {
|
|
8
|
+
defineCustomElement,
|
|
9
|
+
defineCustomElementAlias,
|
|
10
|
+
escapeHtml,
|
|
11
|
+
HTMLElementBase,
|
|
12
|
+
} from "../core/dom";
|
|
13
|
+
import { VIKING_INPUT_STYLES } from "../core/styles";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Framework-agnostic Viking input Web Component with form association.
|
|
17
|
+
* Tag: `viking-input` (legacy alias: `viking-input-wc`)
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* <viking-input placeholder="Email" name="email" type="email"></viking-input>
|
|
21
|
+
*/
|
|
22
|
+
export class VikingInputWc extends HTMLElementBase {
|
|
23
|
+
static readonly formAssociated = true;
|
|
24
|
+
static readonly tag = "viking-input";
|
|
25
|
+
static readonly legacyTag = "viking-input-wc";
|
|
26
|
+
|
|
27
|
+
static get observedAttributes(): string[] {
|
|
28
|
+
return [
|
|
29
|
+
"type",
|
|
30
|
+
"placeholder",
|
|
31
|
+
"value",
|
|
32
|
+
"disabled",
|
|
33
|
+
"loading",
|
|
34
|
+
"clearable",
|
|
35
|
+
"name",
|
|
36
|
+
"autocomplete",
|
|
37
|
+
"required",
|
|
38
|
+
"readonly",
|
|
39
|
+
"minlength",
|
|
40
|
+
"maxlength",
|
|
41
|
+
"pattern",
|
|
42
|
+
"error",
|
|
43
|
+
"aria-label",
|
|
44
|
+
"aria-describedby",
|
|
45
|
+
"bare",
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private readonly shadow: ShadowRoot;
|
|
50
|
+
private readonly internals: ElementInternals | null;
|
|
51
|
+
private input: HTMLInputElement | null = null;
|
|
52
|
+
|
|
53
|
+
constructor() {
|
|
54
|
+
super();
|
|
55
|
+
this.shadow = this.attachShadow({ mode: "open" });
|
|
56
|
+
this.internals = attachElementInternals(this);
|
|
57
|
+
attachShadowStyles(this.shadow, VIKING_INPUT_STYLES);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
connectedCallback(): void {
|
|
61
|
+
this.render();
|
|
62
|
+
this.syncFormValue();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
attributeChangedCallback(name: string): void {
|
|
66
|
+
if (!this.isConnected) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (name === "value" && this.input) {
|
|
70
|
+
this.input.value = this.getAttribute("value") ?? "";
|
|
71
|
+
this.syncFormValue();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.render();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
get value(): string {
|
|
78
|
+
return this.input?.value ?? this.getAttribute("value") ?? "";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
set value(next: string) {
|
|
82
|
+
const normalized = next ?? "";
|
|
83
|
+
this.setAttribute("value", normalized);
|
|
84
|
+
if (this.input) {
|
|
85
|
+
this.input.value = normalized;
|
|
86
|
+
}
|
|
87
|
+
this.syncFormValue();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private get disabled(): boolean {
|
|
91
|
+
return readBoolAttr(this, "disabled");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private get loading(): boolean {
|
|
95
|
+
return readBoolAttr(this, "loading");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private get clearable(): boolean {
|
|
99
|
+
return readBoolAttr(this, "clearable");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private get bare(): boolean {
|
|
103
|
+
return readBoolAttr(this, "bare");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private readonly onInput = (): void => {
|
|
107
|
+
const next = this.input?.value ?? "";
|
|
108
|
+
this.setAttribute("value", next);
|
|
109
|
+
this.syncFormValue();
|
|
110
|
+
this.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
|
|
111
|
+
this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
private readonly onBlur = (): void => {
|
|
115
|
+
this.dispatchEvent(new Event("blur", { bubbles: true, composed: true }));
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
private readonly onClear = (): void => {
|
|
119
|
+
this.value = "";
|
|
120
|
+
this.input?.focus();
|
|
121
|
+
this.dispatchEvent(
|
|
122
|
+
new CustomEvent("viking-cleared", { bubbles: true, composed: true }),
|
|
123
|
+
);
|
|
124
|
+
this.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
private syncFormValue(): void {
|
|
128
|
+
setFormValue(this.internals, this.value);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private render(): void {
|
|
132
|
+
const shellClasses = [
|
|
133
|
+
"viking-input-shell",
|
|
134
|
+
this.disabled ? "viking-disabled" : "",
|
|
135
|
+
this.loading ? "viking-loading" : "",
|
|
136
|
+
]
|
|
137
|
+
.filter(Boolean)
|
|
138
|
+
.join(" ");
|
|
139
|
+
|
|
140
|
+
const type = escapeHtml(this.getAttribute("type") ?? "text");
|
|
141
|
+
const rawPlaceholder = this.getAttribute("placeholder") ?? "";
|
|
142
|
+
const placeholder = escapeHtml(rawPlaceholder);
|
|
143
|
+
const value = escapeHtml(this.getAttribute("value") ?? "");
|
|
144
|
+
const label =
|
|
145
|
+
this.getAttribute("aria-label") ?? (rawPlaceholder || "Text input");
|
|
146
|
+
const autocomplete = escapeHtml(this.getAttribute("autocomplete") ?? "");
|
|
147
|
+
const describedBy = escapeHtml(this.getAttribute("aria-describedby") ?? "");
|
|
148
|
+
const minLength = escapeHtml(this.getAttribute("minlength") ?? "");
|
|
149
|
+
const maxLength = escapeHtml(this.getAttribute("maxlength") ?? "");
|
|
150
|
+
const pattern = escapeHtml(this.getAttribute("pattern") ?? "");
|
|
151
|
+
const error = this.getAttribute("error") ?? "";
|
|
152
|
+
const required = readBoolAttr(this, "required");
|
|
153
|
+
const readonly = readBoolAttr(this, "readonly");
|
|
154
|
+
const showClear =
|
|
155
|
+
this.clearable && value.length > 0 && !this.loading && !this.bare;
|
|
156
|
+
const nativeAttrs = `
|
|
157
|
+
${this.disabled || this.loading ? "disabled" : ""}
|
|
158
|
+
${required ? "required" : ""}
|
|
159
|
+
${readonly ? "readonly" : ""}
|
|
160
|
+
aria-label="${escapeHtml(label)}"
|
|
161
|
+
${describedBy ? `aria-describedby="${describedBy}"` : ""}
|
|
162
|
+
${this.loading ? 'aria-busy="true"' : ""}
|
|
163
|
+
${error ? 'aria-invalid="true"' : ""}
|
|
164
|
+
${autocomplete ? `autocomplete="${autocomplete}"` : ""}
|
|
165
|
+
${minLength ? `minlength="${minLength}"` : ""}
|
|
166
|
+
${maxLength ? `maxlength="${maxLength}"` : ""}
|
|
167
|
+
${pattern ? `pattern="${pattern}"` : ""}
|
|
168
|
+
`;
|
|
169
|
+
|
|
170
|
+
if (this.bare) {
|
|
171
|
+
this.shadow.innerHTML = `
|
|
172
|
+
<input
|
|
173
|
+
part="input"
|
|
174
|
+
class="viking-input-native"
|
|
175
|
+
type="${type}"
|
|
176
|
+
placeholder="${placeholder}"
|
|
177
|
+
value="${value}"
|
|
178
|
+
${nativeAttrs}
|
|
179
|
+
/>
|
|
180
|
+
`;
|
|
181
|
+
} else {
|
|
182
|
+
this.shadow.innerHTML = `
|
|
183
|
+
<div class="${shellClasses}" part="shell">
|
|
184
|
+
<slot name="leading"></slot>
|
|
185
|
+
<input
|
|
186
|
+
part="input"
|
|
187
|
+
type="${type}"
|
|
188
|
+
placeholder="${placeholder}"
|
|
189
|
+
value="${value}"
|
|
190
|
+
${nativeAttrs}
|
|
191
|
+
/>
|
|
192
|
+
${this.loading ? '<span class="viking-input-spinner" aria-hidden="true"></span>' : ""}
|
|
193
|
+
${showClear ? '<button type="button" class="viking-input-clear" aria-label="Clear input" part="clear">×</button>' : ""}
|
|
194
|
+
<slot name="trailing"></slot>
|
|
195
|
+
</div>
|
|
196
|
+
`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.input = this.shadow.querySelector("input");
|
|
200
|
+
this.input?.addEventListener("input", this.onInput);
|
|
201
|
+
this.input?.addEventListener("blur", this.onBlur);
|
|
202
|
+
|
|
203
|
+
const clearBtn = this.shadow.querySelector(".viking-input-clear");
|
|
204
|
+
clearBtn?.addEventListener("click", this.onClear);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export const registerVikingInputWc = (): void => {
|
|
209
|
+
defineCustomElement(VikingInputWc.tag, VikingInputWc);
|
|
210
|
+
defineCustomElementAlias(VikingInputWc.legacyTag, VikingInputWc);
|
|
211
|
+
};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attachShadowStyles,
|
|
3
|
+
closeModalDialog,
|
|
4
|
+
showModalDialog,
|
|
5
|
+
} from "../core/base";
|
|
6
|
+
import {
|
|
7
|
+
defineCustomElement,
|
|
8
|
+
defineCustomElementAlias,
|
|
9
|
+
escapeHtml,
|
|
10
|
+
HTMLElementBase,
|
|
11
|
+
} from "../core/dom";
|
|
12
|
+
import { renderInlineIcon } from "../core/icons-inline";
|
|
13
|
+
import { VIKING_MODAL_STYLES } from "../core/styles";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Framework-agnostic modal Web Component using native `<dialog>` semantics.
|
|
17
|
+
* Tag: `viking-modal` (legacy aliases: `viking-modal-wc`, `viking-dialog`)
|
|
18
|
+
*
|
|
19
|
+
* @attr open - When present, shows the modal
|
|
20
|
+
* @attr title - Dialog title for aria-label / heading
|
|
21
|
+
* @attr dismissible - Allow Escape and backdrop click to close (default: true)
|
|
22
|
+
*
|
|
23
|
+
* @slot default - Modal body content
|
|
24
|
+
* @slot actions - Footer action buttons
|
|
25
|
+
*
|
|
26
|
+
* @method openModal() - Programmatically open
|
|
27
|
+
* @method closeModal() - Programmatically close
|
|
28
|
+
*
|
|
29
|
+
* @event viking-close - Fired when the dialog closes
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* <viking-modal title="Confirm deploy">
|
|
33
|
+
* <p>Push v2.0.0 to production?</p>
|
|
34
|
+
* <viking-button slot="actions" variant="primary">Deploy</viking-button>
|
|
35
|
+
* </viking-modal>
|
|
36
|
+
*/
|
|
37
|
+
export class VikingModalWc extends HTMLElementBase {
|
|
38
|
+
static readonly tag = "viking-modal";
|
|
39
|
+
static readonly legacyTag = "viking-modal-wc";
|
|
40
|
+
static readonly dialogTag = "viking-dialog";
|
|
41
|
+
|
|
42
|
+
static get observedAttributes(): string[] {
|
|
43
|
+
return ["open", "title", "dismissible"];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private readonly shadow: ShadowRoot;
|
|
47
|
+
private dialogEl: HTMLDialogElement | null = null;
|
|
48
|
+
|
|
49
|
+
constructor() {
|
|
50
|
+
super();
|
|
51
|
+
this.shadow = this.attachShadow({ mode: "open" });
|
|
52
|
+
attachShadowStyles(this.shadow, VIKING_MODAL_STYLES);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
connectedCallback(): void {
|
|
56
|
+
this.render();
|
|
57
|
+
this.syncOpen();
|
|
58
|
+
this.dialogEl?.addEventListener("close", this.onClose);
|
|
59
|
+
this.dialogEl?.addEventListener("click", this.onBackdropClick);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
disconnectedCallback(): void {
|
|
63
|
+
this.dialogEl?.removeEventListener("close", this.onClose);
|
|
64
|
+
this.dialogEl?.removeEventListener("click", this.onBackdropClick);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
attributeChangedCallback(name: string): void {
|
|
68
|
+
if (!this.isConnected) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (name === "open") {
|
|
72
|
+
this.syncOpen();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (name === "title") {
|
|
76
|
+
this.updateTitle();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (name === "dismissible") {
|
|
80
|
+
this.render();
|
|
81
|
+
this.syncOpen();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Opens the modal dialog. */
|
|
86
|
+
openModal(): void {
|
|
87
|
+
this.setAttribute("open", "");
|
|
88
|
+
this.syncOpen();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Closes the modal dialog. */
|
|
92
|
+
closeModal(): void {
|
|
93
|
+
this.removeAttribute("open");
|
|
94
|
+
closeModalDialog(this.dialogEl);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** @deprecated Use openModal() */
|
|
98
|
+
open(): void {
|
|
99
|
+
this.openModal();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** @deprecated Use closeModal() */
|
|
103
|
+
close(): void {
|
|
104
|
+
this.closeModal();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private get dismissible(): boolean {
|
|
108
|
+
return this.getAttribute("dismissible") !== "false";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private readonly onClose = (): void => {
|
|
112
|
+
this.removeAttribute("open");
|
|
113
|
+
this.dispatchEvent(
|
|
114
|
+
new CustomEvent("viking-close", { bubbles: true, composed: true }),
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
private readonly onBackdropClick = (event: MouseEvent): void => {
|
|
119
|
+
if (!this.dismissible) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (event.target === this.dialogEl) {
|
|
123
|
+
this.closeModal();
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
private syncOpen(): void {
|
|
128
|
+
if (!this.dialogEl) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const shouldOpen = this.hasAttribute("open");
|
|
132
|
+
if (shouldOpen && !this.dialogEl.open) {
|
|
133
|
+
showModalDialog(this.dialogEl);
|
|
134
|
+
queueMicrotask(() => {
|
|
135
|
+
const closeBtn = this.shadow.querySelector<HTMLButtonElement>(
|
|
136
|
+
".viking-modal-close",
|
|
137
|
+
);
|
|
138
|
+
(closeBtn ?? this.dialogEl)?.focus();
|
|
139
|
+
});
|
|
140
|
+
} else if (!shouldOpen && this.dialogEl.open) {
|
|
141
|
+
closeModalDialog(this.dialogEl);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private updateTitle(): void {
|
|
146
|
+
const title = this.getAttribute("title") ?? "Dialog";
|
|
147
|
+
const heading = this.shadow.querySelector(".viking-modal-heading");
|
|
148
|
+
if (heading) {
|
|
149
|
+
heading.textContent = title;
|
|
150
|
+
}
|
|
151
|
+
this.dialogEl?.setAttribute("aria-label", title);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private render(): void {
|
|
155
|
+
const title = this.getAttribute("title") ?? "Dialog";
|
|
156
|
+
|
|
157
|
+
this.shadow.innerHTML = `
|
|
158
|
+
<dialog class="viking-modal-backdrop" aria-label="${escapeHtml(title)}" aria-modal="true">
|
|
159
|
+
<div class="viking-modal-panel" part="panel" role="document">
|
|
160
|
+
<header class="viking-modal-header" part="header">
|
|
161
|
+
<h2 class="viking-modal-heading" part="title">${escapeHtml(title)}</h2>
|
|
162
|
+
${this.dismissible ? `<button type="button" class="viking-modal-close" part="close" aria-label="Close dialog">${renderInlineIcon("x", 20)}</button>` : ""}
|
|
163
|
+
</header>
|
|
164
|
+
<div class="viking-modal-body" part="body"><slot></slot></div>
|
|
165
|
+
<footer class="viking-modal-footer" part="footer"><slot name="actions"></slot></footer>
|
|
166
|
+
</div>
|
|
167
|
+
</dialog>
|
|
168
|
+
`;
|
|
169
|
+
|
|
170
|
+
this.dialogEl = this.shadow.querySelector("dialog");
|
|
171
|
+
this.shadow
|
|
172
|
+
.querySelector(".viking-modal-close")
|
|
173
|
+
?.addEventListener("click", () => this.closeModal());
|
|
174
|
+
this.dialogEl?.addEventListener("keydown", (event) => {
|
|
175
|
+
if (event.key === "Escape" && this.dismissible) {
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
this.closeModal();
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export const registerVikingModalWc = (): void => {
|
|
184
|
+
defineCustomElement(VikingModalWc.tag, VikingModalWc);
|
|
185
|
+
defineCustomElementAlias(VikingModalWc.legacyTag, VikingModalWc);
|
|
186
|
+
defineCustomElementAlias(VikingModalWc.dialogTag, VikingModalWc);
|
|
187
|
+
};
|