@chiralkit/design-system 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +11 -0
- package/dist/chiralkit-ds/chiralkit-ds.css +1 -0
- package/dist/chiralkit-ds/chiralkit-ds.esm.js +1 -0
- package/dist/chiralkit-ds/index.esm.js +1 -0
- package/dist/chiralkit-ds/p-038fa008.entry.js +1 -0
- package/dist/chiralkit-ds/p-2fdc1012.entry.js +1 -0
- package/dist/chiralkit-ds/p-306ec33b.entry.js +1 -0
- package/dist/chiralkit-ds/p-3f704698.entry.js +1 -0
- package/dist/chiralkit-ds/p-41081d22.entry.js +1 -0
- package/dist/chiralkit-ds/p-7d7e5aac.entry.js +1 -0
- package/dist/chiralkit-ds/p-84b801f8.entry.js +1 -0
- package/dist/chiralkit-ds/p-a28c71b4.entry.js +1 -0
- package/dist/chiralkit-ds/p-be84312f.entry.js +1 -0
- package/dist/chiralkit-ds/p-c8015fe6.entry.js +1 -0
- package/dist/chiralkit-ds/p-d36a6bea.entry.js +1 -0
- package/dist/chiralkit-ds/p-e-glNjK6.js +2 -0
- package/dist/chiralkit-ds/p-f0f9ff61.entry.js +1 -0
- package/dist/cjs/chiralkit-ds.cjs.js +24 -0
- package/dist/cjs/ds-badge.cjs.entry.js +18 -0
- package/dist/cjs/ds-button.cjs.entry.js +26 -0
- package/dist/cjs/ds-card.cjs.entry.js +17 -0
- package/dist/cjs/ds-checkbox.cjs.entry.js +67 -0
- package/dist/cjs/ds-code-block.cjs.entry.js +39 -0
- package/dist/cjs/ds-dialog.cjs.entry.js +67 -0
- package/dist/cjs/ds-input.cjs.entry.js +50 -0
- package/dist/cjs/ds-loader.cjs.entry.js +26 -0
- package/dist/cjs/ds-select.cjs.entry.js +85 -0
- package/dist/cjs/ds-tabs.cjs.entry.js +138 -0
- package/dist/cjs/ds-textarea.cjs.entry.js +50 -0
- package/dist/cjs/ds-tooltip.cjs.entry.js +233 -0
- package/dist/cjs/index-mMvyq7U_.js +2018 -0
- package/dist/cjs/index.cjs.js +10 -0
- package/dist/cjs/loader.cjs.js +12 -0
- package/dist/collection/collection-manifest.json +24 -0
- package/dist/collection/components/ds-badge/ds-badge.css +36 -0
- package/dist/collection/components/ds-badge/ds-badge.js +93 -0
- package/dist/collection/components/ds-button/ds-button.css +66 -0
- package/dist/collection/components/ds-button/ds-button.js +197 -0
- package/dist/collection/components/ds-card/ds-card.css +12 -0
- package/dist/collection/components/ds-card/ds-card.js +68 -0
- package/dist/collection/components/ds-checkbox/ds-checkbox.css +22 -0
- package/dist/collection/components/ds-checkbox/ds-checkbox.js +339 -0
- package/dist/collection/components/ds-code-block/ds-code-block.css +65 -0
- package/dist/collection/components/ds-code-block/ds-code-block.js +162 -0
- package/dist/collection/components/ds-dialog/ds-dialog.css +30 -0
- package/dist/collection/components/ds-dialog/ds-dialog.js +313 -0
- package/dist/collection/components/ds-input/ds-input.css +38 -0
- package/dist/collection/components/ds-input/ds-input.js +353 -0
- package/dist/collection/components/ds-loader/ds-loader.css +94 -0
- package/dist/collection/components/ds-loader/ds-loader.js +149 -0
- package/dist/collection/components/ds-select/ds-select.css +69 -0
- package/dist/collection/components/ds-select/ds-select.js +235 -0
- package/dist/collection/components/ds-tabs/ds-tabs.css +59 -0
- package/dist/collection/components/ds-tabs/ds-tabs.js +248 -0
- package/dist/collection/components/ds-textarea/ds-textarea.css +41 -0
- package/dist/collection/components/ds-textarea/ds-textarea.js +342 -0
- package/dist/collection/components/ds-tooltip/ds-tooltip.css +31 -0
- package/dist/collection/components/ds-tooltip/ds-tooltip.js +318 -0
- package/dist/collection/components.js +2 -0
- package/dist/collection/index.js +1 -0
- package/dist/components/ds-badge.d.ts +11 -0
- package/dist/components/ds-badge.js +1 -0
- package/dist/components/ds-button.d.ts +11 -0
- package/dist/components/ds-button.js +1 -0
- package/dist/components/ds-card.d.ts +11 -0
- package/dist/components/ds-card.js +1 -0
- package/dist/components/ds-checkbox.d.ts +11 -0
- package/dist/components/ds-checkbox.js +1 -0
- package/dist/components/ds-code-block.d.ts +11 -0
- package/dist/components/ds-code-block.js +1 -0
- package/dist/components/ds-dialog.d.ts +11 -0
- package/dist/components/ds-dialog.js +1 -0
- package/dist/components/ds-input.d.ts +11 -0
- package/dist/components/ds-input.js +1 -0
- package/dist/components/ds-loader.d.ts +11 -0
- package/dist/components/ds-loader.js +1 -0
- package/dist/components/ds-select.d.ts +11 -0
- package/dist/components/ds-select.js +1 -0
- package/dist/components/ds-tabs.d.ts +11 -0
- package/dist/components/ds-tabs.js +1 -0
- package/dist/components/ds-textarea.d.ts +11 -0
- package/dist/components/ds-textarea.js +1 -0
- package/dist/components/ds-tooltip.d.ts +11 -0
- package/dist/components/ds-tooltip.js +1 -0
- package/dist/components/index.d.ts +35 -0
- package/dist/components/index.js +1 -0
- package/dist/components/p-DDUZow8F.js +1 -0
- package/dist/components/p-ZsYP0VBq.js +1 -0
- package/dist/components/p-k0ywxWpC.js +1 -0
- package/dist/esm/chiralkit-ds.js +20 -0
- package/dist/esm/ds-badge.entry.js +16 -0
- package/dist/esm/ds-button.entry.js +24 -0
- package/dist/esm/ds-card.entry.js +15 -0
- package/dist/esm/ds-checkbox.entry.js +65 -0
- package/dist/esm/ds-code-block.entry.js +37 -0
- package/dist/esm/ds-dialog.entry.js +65 -0
- package/dist/esm/ds-input.entry.js +48 -0
- package/dist/esm/ds-loader.entry.js +24 -0
- package/dist/esm/ds-select.entry.js +83 -0
- package/dist/esm/ds-tabs.entry.js +136 -0
- package/dist/esm/ds-textarea.entry.js +48 -0
- package/dist/esm/ds-tooltip.entry.js +231 -0
- package/dist/esm/index-e-glNjK6.js +2008 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/loader.js +10 -0
- package/dist/index.cjs.js +1 -0
- package/dist/index.js +1 -0
- package/dist/layouts/app-shell.css +84 -0
- package/dist/tests/a11y.js +55 -0
- package/dist/types/Users/alex/projects/MyGitHub/chiralkit/packages/design-system/.stencil/tests/a11y.d.ts +17 -0
- package/dist/types/components/ds-badge/ds-badge.d.ts +54 -0
- package/dist/types/components/ds-button/ds-button.d.ts +75 -0
- package/dist/types/components/ds-card/ds-card.d.ts +53 -0
- package/dist/types/components/ds-checkbox/ds-checkbox.d.ts +94 -0
- package/dist/types/components/ds-code-block/ds-code-block.d.ts +83 -0
- package/dist/types/components/ds-dialog/ds-dialog.d.ts +148 -0
- package/dist/types/components/ds-input/ds-input.d.ts +104 -0
- package/dist/types/components/ds-loader/ds-loader.d.ts +64 -0
- package/dist/types/components/ds-select/ds-select.d.ts +123 -0
- package/dist/types/components/ds-tabs/ds-tabs.d.ts +155 -0
- package/dist/types/components/ds-textarea/ds-textarea.d.ts +93 -0
- package/dist/types/components/ds-tooltip/ds-tooltip.d.ts +26 -0
- package/dist/types/components.d.ts +3068 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/stencil-public-runtime.d.ts +1861 -0
- package/docs.json +2112 -0
- package/loader/cdn.js +1 -0
- package/loader/index.cjs.js +1 -0
- package/loader/index.d.ts +24 -0
- package/loader/index.es2017.js +1 -0
- package/loader/index.js +2 -0
- package/package.json +78 -0
- package/react/components/ds-badge.tsx +25 -0
- package/react/components/ds-button.tsx +25 -0
- package/react/components/ds-card.tsx +25 -0
- package/react/components/ds-checkbox.tsx +25 -0
- package/react/components/ds-code-block.tsx +25 -0
- package/react/components/ds-dialog.tsx +31 -0
- package/react/components/ds-input.tsx +25 -0
- package/react/components/ds-loader.tsx +25 -0
- package/react/components/ds-select.tsx +25 -0
- package/react/components/ds-tabs.tsx +25 -0
- package/react/components/ds-textarea.tsx +25 -0
- package/react/components/ds-tooltip.tsx +25 -0
- package/react/components.ts +162 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { r as registerInstance, c as createEvent, h, H as Host } from './index-e-glNjK6.js';
|
|
2
|
+
|
|
3
|
+
const dsDialogCss = () => `:host{display:contents}.ds-dialog{color:inherit;background-color:color-mix(in oklch, currentColor 4%, Canvas);border:1px solid color-mix(in oklch, currentColor 15%, transparent);border-radius:var(--ds-radius-md);padding:var(--ds-spacing-xl);box-sizing:border-box;max-width:min(90vw, 32rem);max-height:90vh;overflow:auto}.ds-dialog::backdrop{background-color:rgba(0, 0, 0, 0.5)}`;
|
|
4
|
+
|
|
5
|
+
const DsDialog = class {
|
|
6
|
+
constructor(hostRef) {
|
|
7
|
+
registerInstance(this, hostRef);
|
|
8
|
+
this.dsOpen = createEvent(this, "dsOpen");
|
|
9
|
+
this.dsClose = createEvent(this, "dsClose");
|
|
10
|
+
}
|
|
11
|
+
dialogEl;
|
|
12
|
+
open = false;
|
|
13
|
+
label;
|
|
14
|
+
labelledBy;
|
|
15
|
+
describedBy;
|
|
16
|
+
dsOpen;
|
|
17
|
+
dsClose;
|
|
18
|
+
watchOpen(newValue) {
|
|
19
|
+
this.syncOpen(newValue);
|
|
20
|
+
}
|
|
21
|
+
componentDidLoad() {
|
|
22
|
+
// The `close` event on the native <dialog> is not composed by
|
|
23
|
+
// default, so it does not bubble out of the shadow root. Wire it
|
|
24
|
+
// here rather than in the JSX so we can re-emit as a Stencil
|
|
25
|
+
// Event that composed-crosses the boundary.
|
|
26
|
+
this.dialogEl?.addEventListener("close", this.onNativeClose);
|
|
27
|
+
if (this.open) {
|
|
28
|
+
this.syncOpen(true);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
disconnectedCallback() {
|
|
32
|
+
this.dialogEl?.removeEventListener("close", this.onNativeClose);
|
|
33
|
+
}
|
|
34
|
+
syncOpen(shouldOpen) {
|
|
35
|
+
if (!this.dialogEl)
|
|
36
|
+
return;
|
|
37
|
+
const isOpen = this.dialogEl.open;
|
|
38
|
+
if (shouldOpen && !isOpen) {
|
|
39
|
+
// showModal() is the modal form — traps focus, adds the ::backdrop,
|
|
40
|
+
// makes background inert. show() (non-modal) is intentionally not
|
|
41
|
+
// exposed in v1.
|
|
42
|
+
this.dialogEl.showModal();
|
|
43
|
+
this.dsOpen.emit();
|
|
44
|
+
}
|
|
45
|
+
else if (!shouldOpen && isOpen) {
|
|
46
|
+
this.dialogEl.close();
|
|
47
|
+
// dsClose is emitted by onNativeClose to also cover Escape /
|
|
48
|
+
// form-submit paths — no need to emit here.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
onNativeClose = () => {
|
|
52
|
+
this.dsClose.emit();
|
|
53
|
+
};
|
|
54
|
+
render() {
|
|
55
|
+
return (h(Host, { key: '8b25b70865ea028c81405f77a4d016348507aae6' }, h("dialog", { key: '0c82754fe4e4ecf68232a93616bbff3621866375', class: "ds-dialog", "aria-label": this.label, "aria-labelledby": this.labelledBy, "aria-describedby": this.describedBy, ref: (el) => (this.dialogEl = el) }, h("slot", { key: '30131eed9eec7db67a407770b5caefe42c78b68a' }))));
|
|
56
|
+
}
|
|
57
|
+
static get watchers() { return {
|
|
58
|
+
"open": [{
|
|
59
|
+
"watchOpen": 0
|
|
60
|
+
}]
|
|
61
|
+
}; }
|
|
62
|
+
};
|
|
63
|
+
DsDialog.style = dsDialogCss();
|
|
64
|
+
|
|
65
|
+
export { DsDialog as ds_dialog };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { r as registerInstance, c as createEvent, h, H as Host } from './index-e-glNjK6.js';
|
|
2
|
+
|
|
3
|
+
const dsInputCss = () => `:host{display:inline-block}.ds-input{font-family:var(--ds-font-family);font-size:var(--ds-font-size-md);background-color:transparent;color:inherit;border:1px solid color-mix(in oklch, currentColor 22%, transparent);-webkit-appearance:none;appearance:none;box-sizing:border-box;width:100%}.ds-input::placeholder{color:color-mix(in oklch, currentColor 45%, transparent)}.ds-input:disabled{opacity:0.5;cursor:not-allowed}.ds-input[aria-invalid="true"]{border-color:color-mix(in oklch, currentColor 55%, transparent)}.ds-input--md{border-radius:var(--ds-radius-sm);padding:var(--ds-spacing-sm) var(--ds-spacing-lg)}.ds-input--lg{border-radius:var(--ds-radius-md);padding:var(--ds-spacing-md) var(--ds-spacing-xl)}`;
|
|
4
|
+
|
|
5
|
+
const DsInput = class {
|
|
6
|
+
constructor(hostRef) {
|
|
7
|
+
registerInstance(this, hostRef);
|
|
8
|
+
this.dsInput = createEvent(this, "dsInput");
|
|
9
|
+
}
|
|
10
|
+
inputEl;
|
|
11
|
+
value;
|
|
12
|
+
name;
|
|
13
|
+
type = "text";
|
|
14
|
+
placeholder;
|
|
15
|
+
disabled = false;
|
|
16
|
+
required = false;
|
|
17
|
+
invalid = false;
|
|
18
|
+
describedBy;
|
|
19
|
+
label;
|
|
20
|
+
size = "md";
|
|
21
|
+
dsInput;
|
|
22
|
+
watchValue(newValue) {
|
|
23
|
+
if (this.inputEl && this.inputEl.value !== (newValue ?? "")) {
|
|
24
|
+
this.inputEl.value = newValue ?? "";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
componentDidLoad() {
|
|
28
|
+
if (this.inputEl && this.value !== undefined) {
|
|
29
|
+
this.inputEl.value = this.value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
onInput = (event) => {
|
|
33
|
+
const value = event.target.value;
|
|
34
|
+
this.dsInput.emit(value);
|
|
35
|
+
};
|
|
36
|
+
render() {
|
|
37
|
+
const cls = `ds-input ds-input--${this.size}`;
|
|
38
|
+
return (h(Host, { key: 'efce39c6ecfb8dda71577e7fb7b285ddc16287f5' }, h("input", { key: '5f6ad573f20e6eca20f42aa96c8acdf386508d1c', class: cls, type: this.type, name: this.name, placeholder: this.placeholder, disabled: this.disabled, "aria-label": this.label, "aria-required": this.required ? "true" : undefined, "aria-invalid": this.invalid ? "true" : undefined, "aria-describedby": this.describedBy, ref: (el) => (this.inputEl = el), onInput: this.onInput })));
|
|
39
|
+
}
|
|
40
|
+
static get watchers() { return {
|
|
41
|
+
"value": [{
|
|
42
|
+
"watchValue": 0
|
|
43
|
+
}]
|
|
44
|
+
}; }
|
|
45
|
+
};
|
|
46
|
+
DsInput.style = dsInputCss();
|
|
47
|
+
|
|
48
|
+
export { DsInput as ds_input };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { r as registerInstance, h, H as Host } from './index-e-glNjK6.js';
|
|
2
|
+
|
|
3
|
+
const dsLoaderCss = () => `:host{display:inline-flex;color:currentColor;line-height:0}.ds-loader{display:block;width:40px;height:40px;color:inherit}.ds-loader--sm{width:16px;height:16px}.ds-loader--md{width:40px;height:40px}.ds-loader--lg{width:120px;height:120px}.ds-loader__half{transform-box:view-box;transform-origin:50% 50%;animation:ds-loader-swing 1.4s cubic-bezier(0.6, 0, 0.4, 1) infinite}.ds-loader__half--b{animation-name:ds-loader-swing-mirror}.ds-loader__mirror{transform-box:view-box;transform-origin:50% 50%;transform:scaleX(-1)}.ds-loader--speed-slow .ds-loader__half{animation-duration:2.2s}.ds-loader--speed-fast .ds-loader__half{animation-duration:0.9s}@keyframes ds-loader-swing{0%{transform:rotate(180deg)}50%{transform:rotate(0deg)}100%{transform:rotate(180deg)}}@keyframes ds-loader-swing-mirror{0%{transform:rotate(-180deg)}50%{transform:rotate(0deg)}100%{transform:rotate(-180deg)}}@media (prefers-reduced-motion: reduce){.ds-loader__half,.ds-loader__half--b{animation:ds-loader-pulse 2s ease-in-out infinite}@keyframes ds-loader-pulse{0%,100%{opacity:0.4;transform:rotate(0deg)}50%{opacity:1;transform:rotate(0deg)}}}`;
|
|
4
|
+
|
|
5
|
+
const DsLoader = class {
|
|
6
|
+
constructor(hostRef) {
|
|
7
|
+
registerInstance(this, hostRef);
|
|
8
|
+
}
|
|
9
|
+
size = "md";
|
|
10
|
+
label = "Loading";
|
|
11
|
+
speed = "normal";
|
|
12
|
+
render() {
|
|
13
|
+
const cls = [
|
|
14
|
+
"ds-loader",
|
|
15
|
+
`ds-loader--${this.size}`,
|
|
16
|
+
`ds-loader--speed-${this.speed}`,
|
|
17
|
+
].join(" ");
|
|
18
|
+
return (h(Host, { key: 'd7017e7e3c970e27bffa30c193abd937c5ec13cd', role: "status", "aria-label": this.label }, h("svg", { key: '9eb10695249bc4c833b6347d38226fdc37d4be7d', class: cls, viewBox: "-48 -48 96 96", "aria-hidden": "true", focusable: "false" }, h("g", { key: '711ee60648593d216687087c5b2145ec25120bc5', class: "ds-loader__half ds-loader__half--a" }, h("path", { key: '9efdc31104b6037a57d7bdc802303938f7285666', d: LOGO_PATH_D, fill: "none", stroke: "currentColor", "stroke-width": "14", "stroke-linecap": "round", "stroke-linejoin": "round" })), h("g", { key: 'fcbeefc154e0e9db5b0378f77dafa3f9c0162e92', class: "ds-loader__half ds-loader__half--b" }, h("g", { key: '790b3ec1c4b262352cc383ab6b9f7d5cbb77474b', class: "ds-loader__mirror" }, h("path", { key: '694713be37532568852b2de2530cc50c1ff3b8de', d: LOGO_PATH_D, fill: "none", stroke: "currentColor", "stroke-width": "14", "stroke-linecap": "round", "stroke-linejoin": "round" }))))));
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const LOGO_PATH_D = "M14.09 -27.22 L13.04 -28.53 L11.94 -29.76 L10.78 -30.92 L9.57 -32.01 L8.30 -33.02 L7.00 -33.95 L5.65 -34.80 L4.27 -35.57 L2.85 -36.25 L1.40 -36.84 L-0.07 -37.35 L-1.56 -37.76 L-3.07 -38.08 L-4.59 -38.31 L-6.13 -38.45 L-7.66 -38.50 L-9.20 -38.45 L-10.73 -38.31 L-12.25 -38.08 L-13.76 -37.76 L-15.25 -37.35 L-16.73 -36.84 L-18.17 -36.25 L-19.59 -35.57 L-20.97 -34.80 L-22.32 -33.95 L-23.63 -33.02 L-24.89 -32.01 L-26.10 -30.92 L-27.26 -29.76 L-28.37 -28.53 L-29.41 -27.22 L-30.40 -25.86 L-31.32 -24.42 L-32.17 -22.93 L-32.95 -21.39 L-33.66 -19.79 L-34.30 -18.15 L-34.86 -16.46 L-35.34 -14.73 L-35.74 -12.97 L-36.06 -11.18 L-36.29 -9.35 L-36.44 -7.51 L-36.50 -5.65 L-36.48 -3.77 L-36.37 -1.89 L-36.18 0 L-35.90 1.89 L-35.53 3.77 L-35.07 5.65 L-34.53 7.51 L-33.90 9.35 L-33.18 11.18 L-32.39 12.97 L-31.51 14.73 L-30.55 16.46 L-29.51 18.15 L-28.40 19.79 L-27.21 21.39 L-25.95 22.93 L-24.61 24.42 L-23.22 25.86 L-21.75 27.22 L-20.23 28.53 L-18.64 29.76 L-17.00 30.92 L-15.31 32.01 L-13.57 33.02 L-11.79 33.95 L-9.96 34.80 L-8.10 35.57 L-6.20 36.25 L-4.28 36.84 L-2.33 37.35 L-0.35 37.76 L1.63 38.08 L3.64 38.31 L5.65 38.45 L7.66 38.50 L9.68 38.45 L11.69 38.31 L13.69 38.08 L15.68 37.76 L17.65 37.35 L19.60 36.84 L21.53 36.25 L23.42 35.57 L25.28 34.80 L27.11 33.95 L28.89 33.02 L30.63 32.01 L32.32 30.92 L33.96 29.76 L35.55 28.53 L37.07 27.22";
|
|
22
|
+
DsLoader.style = dsLoaderCss();
|
|
23
|
+
|
|
24
|
+
export { DsLoader as ds_loader };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { r as registerInstance, c as createEvent, h, H as Host } from './index-e-glNjK6.js';
|
|
2
|
+
|
|
3
|
+
const dsSelectCss = () => `:host{display:inline-block;position:relative}.ds-select{font-family:var(--ds-font-family);font-size:var(--ds-font-size-md);background-color:transparent;color:inherit;border:1px solid color-mix(in oklch, currentColor 22%, transparent);cursor:pointer;-webkit-appearance:none;appearance:none}:host::after{content:"";position:absolute;top:50%;right:var(--ds-spacing-sm);width:12px;height:12px;transform:translateY(-50%);background-color:currentColor;-webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'><path d='M2.5 4.5L6 8L9.5 4.5' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'><path d='M2.5 4.5L6 8L9.5 4.5' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-position:center;mask-position:center;-webkit-mask-size:contain;mask-size:contain;pointer-events:none}.ds-select:disabled{opacity:0.5;cursor:not-allowed}:host(:has(.ds-select:disabled))::after{opacity:0.5}.ds-select--md{border-radius:var(--ds-radius-sm);padding:var(--ds-spacing-sm) var(--ds-spacing-lg);padding-right:calc(var(--ds-spacing-lg) + var(--ds-spacing-lg))}.ds-select--lg{border-radius:var(--ds-radius-md);padding:var(--ds-spacing-md) var(--ds-spacing-xl);padding-right:calc(var(--ds-spacing-xl) + var(--ds-spacing-lg))}`;
|
|
4
|
+
|
|
5
|
+
const DsSelect = class {
|
|
6
|
+
constructor(hostRef) {
|
|
7
|
+
registerInstance(this, hostRef);
|
|
8
|
+
this.dsChange = createEvent(this, "dsChange");
|
|
9
|
+
}
|
|
10
|
+
selectEl;
|
|
11
|
+
value;
|
|
12
|
+
name;
|
|
13
|
+
disabled = false;
|
|
14
|
+
size = "md";
|
|
15
|
+
label;
|
|
16
|
+
/**
|
|
17
|
+
* Options rendered inside the internal shadow `<select>`. Accepts either:
|
|
18
|
+
* - a JSON string (SSR-friendly — server-rendered HTML can only set
|
|
19
|
+
* string attributes, and Stencil does not JSON-parse array attributes),
|
|
20
|
+
* e.g. `options='[{"value":"en","label":"EN"}]'`
|
|
21
|
+
* - an array property set from client-side JS,
|
|
22
|
+
* e.g. `el.options = [{ value: 'en', label: 'EN' }]`
|
|
23
|
+
*
|
|
24
|
+
* Malformed JSON fails safe to an empty list rather than throwing, so the
|
|
25
|
+
* component still renders a visible (if empty) select.
|
|
26
|
+
*/
|
|
27
|
+
options = "[]";
|
|
28
|
+
dsChange;
|
|
29
|
+
watchValue(newValue) {
|
|
30
|
+
if (this.selectEl && this.selectEl.value !== newValue) {
|
|
31
|
+
this.selectEl.value = newValue ?? "";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Empty-bodied on purpose: `options` is already a @Prop, so Stencil
|
|
35
|
+
// schedules a re-render on its own whenever it changes — this watcher
|
|
36
|
+
// exists only to document that `options` is reactive. The actual value
|
|
37
|
+
// re-sync happens in componentDidUpdate, once the new <option> elements
|
|
38
|
+
// are actually in the DOM (syncing here, before that render lands, would
|
|
39
|
+
// assign `this.value` against the *old* option list and get dropped).
|
|
40
|
+
watchOptions() { }
|
|
41
|
+
componentDidLoad() {
|
|
42
|
+
this.syncValue();
|
|
43
|
+
}
|
|
44
|
+
componentDidUpdate() {
|
|
45
|
+
this.syncValue();
|
|
46
|
+
}
|
|
47
|
+
syncValue() {
|
|
48
|
+
if (this.selectEl && this.value !== undefined) {
|
|
49
|
+
this.selectEl.value = this.value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
parseOptions() {
|
|
53
|
+
if (typeof this.options === "string") {
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(this.options);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
console.warn("[ds-select] failed to parse options prop:", this.options);
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return this.options;
|
|
63
|
+
}
|
|
64
|
+
onChange = (event) => {
|
|
65
|
+
const value = event.target.value;
|
|
66
|
+
this.dsChange.emit(value);
|
|
67
|
+
};
|
|
68
|
+
render() {
|
|
69
|
+
const cls = `ds-select ds-select--${this.size}`;
|
|
70
|
+
return (h(Host, { key: '9c5755ca98a3a3a1340414de0109133ffb6617c4' }, h("select", { key: '7ac471b32e083e754c398ed22baf8ba993997ee5', class: cls, name: this.name, disabled: this.disabled, "aria-label": this.label, ref: (el) => (this.selectEl = el), onChange: this.onChange }, this.parseOptions().map((o) => (h("option", { value: o.value }, o.label))))));
|
|
71
|
+
}
|
|
72
|
+
static get watchers() { return {
|
|
73
|
+
"value": [{
|
|
74
|
+
"watchValue": 0
|
|
75
|
+
}],
|
|
76
|
+
"options": [{
|
|
77
|
+
"watchOptions": 0
|
|
78
|
+
}]
|
|
79
|
+
}; }
|
|
80
|
+
};
|
|
81
|
+
DsSelect.style = dsSelectCss();
|
|
82
|
+
|
|
83
|
+
export { DsSelect as ds_select };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { r as registerInstance, c as createEvent, h, H as Host } from './index-e-glNjK6.js';
|
|
2
|
+
|
|
3
|
+
const dsTabsCss = () => `ds-tabs{display:block;color:inherit;font-family:var(--ds-font-family);font-size:var(--ds-font-size-md)}.ds-tabs{display:flex;gap:var(--ds-spacing-sm);border-bottom:1px solid color-mix(in oklch, currentColor 15%, transparent)}.ds-tabs--vertical{flex-direction:column;align-items:stretch;border-bottom:none;border-right:1px solid color-mix(in oklch, currentColor 15%, transparent)}.ds-tab{font:inherit;color:inherit;background-color:transparent;border:none;padding:var(--ds-spacing-sm) var(--ds-spacing-lg);cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px}.ds-tabs--vertical .ds-tab{text-align:left;border-bottom:none;border-right:2px solid transparent;margin-bottom:0;margin-right:-1px}.ds-tab--active{border-bottom-color:currentColor}.ds-tabs--vertical .ds-tab--active{border-bottom-color:transparent;border-right-color:currentColor}.ds-tab:disabled{opacity:0.5;cursor:not-allowed}`;
|
|
4
|
+
|
|
5
|
+
const DsTabs = class {
|
|
6
|
+
constructor(hostRef) {
|
|
7
|
+
registerInstance(this, hostRef);
|
|
8
|
+
this.dsChange = createEvent(this, "dsChange");
|
|
9
|
+
}
|
|
10
|
+
tabRefs = new Map();
|
|
11
|
+
pendingFocusId;
|
|
12
|
+
items = "[]";
|
|
13
|
+
active;
|
|
14
|
+
orientation = "horizontal";
|
|
15
|
+
label;
|
|
16
|
+
dsChange;
|
|
17
|
+
watchItems() {
|
|
18
|
+
// Re-reconcile the active id when items change: if the current
|
|
19
|
+
// active is no longer in the list, fall back to the first enabled.
|
|
20
|
+
const parsed = this.parseItems();
|
|
21
|
+
if (parsed.length === 0)
|
|
22
|
+
return;
|
|
23
|
+
if (!parsed.some((it) => it.id === this.active && !it.disabled)) {
|
|
24
|
+
const firstEnabled = parsed.find((it) => !it.disabled);
|
|
25
|
+
if (firstEnabled) {
|
|
26
|
+
this.active = firstEnabled.id;
|
|
27
|
+
this.dsChange.emit(firstEnabled.id);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
componentWillLoad() {
|
|
32
|
+
const parsed = this.parseItems();
|
|
33
|
+
if (parsed.length === 0)
|
|
34
|
+
return;
|
|
35
|
+
// Reconcile initial active — if the consumer passed an id that
|
|
36
|
+
// isn't in items (or none at all), fall back to the first enabled
|
|
37
|
+
// and emit dsChange once so their state matches ours.
|
|
38
|
+
if (!this.active || !parsed.some((it) => it.id === this.active && !it.disabled)) {
|
|
39
|
+
const firstEnabled = parsed.find((it) => !it.disabled);
|
|
40
|
+
if (firstEnabled) {
|
|
41
|
+
this.active = firstEnabled.id;
|
|
42
|
+
this.dsChange.emit(firstEnabled.id);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
componentDidRender() {
|
|
47
|
+
// Move programmatic focus to the newly selected tab after
|
|
48
|
+
// arrow/Home/End nav so the visible focus ring tracks selection.
|
|
49
|
+
// Click-driven selection does not set pendingFocusId — the native
|
|
50
|
+
// button click already lands focus on the clicked element.
|
|
51
|
+
if (this.pendingFocusId) {
|
|
52
|
+
const target = this.tabRefs.get(this.pendingFocusId);
|
|
53
|
+
target?.focus();
|
|
54
|
+
this.pendingFocusId = undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
parseItems() {
|
|
58
|
+
if (typeof this.items === "string") {
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(this.items);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
console.warn("[ds-tabs] failed to parse items prop:", this.items);
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return this.items;
|
|
68
|
+
}
|
|
69
|
+
enabledItems() {
|
|
70
|
+
return this.parseItems().filter((it) => !it.disabled);
|
|
71
|
+
}
|
|
72
|
+
selectAndFocus(id) {
|
|
73
|
+
if (id === this.active)
|
|
74
|
+
return;
|
|
75
|
+
this.active = id;
|
|
76
|
+
this.pendingFocusId = id;
|
|
77
|
+
this.dsChange.emit(id);
|
|
78
|
+
}
|
|
79
|
+
onClick = (id) => {
|
|
80
|
+
if (id === this.active)
|
|
81
|
+
return;
|
|
82
|
+
this.active = id;
|
|
83
|
+
// No pendingFocusId: click already focused the button natively.
|
|
84
|
+
this.dsChange.emit(id);
|
|
85
|
+
};
|
|
86
|
+
onKeyDown = (event, currentId) => {
|
|
87
|
+
const enabled = this.enabledItems();
|
|
88
|
+
if (enabled.length === 0)
|
|
89
|
+
return;
|
|
90
|
+
const isHorizontal = this.orientation === "horizontal";
|
|
91
|
+
const nextKey = isHorizontal ? "ArrowRight" : "ArrowDown";
|
|
92
|
+
const prevKey = isHorizontal ? "ArrowLeft" : "ArrowUp";
|
|
93
|
+
const currentIdx = enabled.findIndex((it) => it.id === currentId);
|
|
94
|
+
if (event.key === nextKey) {
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
const nextIdx = (currentIdx + 1) % enabled.length;
|
|
97
|
+
this.selectAndFocus(enabled[nextIdx].id);
|
|
98
|
+
}
|
|
99
|
+
else if (event.key === prevKey) {
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
const prevIdx = (currentIdx - 1 + enabled.length) % enabled.length;
|
|
102
|
+
this.selectAndFocus(enabled[prevIdx].id);
|
|
103
|
+
}
|
|
104
|
+
else if (event.key === "Home") {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
this.selectAndFocus(enabled[0].id);
|
|
107
|
+
}
|
|
108
|
+
else if (event.key === "End") {
|
|
109
|
+
event.preventDefault();
|
|
110
|
+
this.selectAndFocus(enabled[enabled.length - 1].id);
|
|
111
|
+
}
|
|
112
|
+
// Enter / Space intentionally not handled — automatic activation
|
|
113
|
+
// already selected the tab on focus move, and the native button
|
|
114
|
+
// click behavior remains available for consumers who bind to it.
|
|
115
|
+
};
|
|
116
|
+
render() {
|
|
117
|
+
const items = this.parseItems();
|
|
118
|
+
return (h(Host, { key: 'efc6a329ddbc14550f7e186480e41218f52b4865' }, h("div", { key: '4d475ddcb89198d97440e1e807acbef5984f658e', class: `ds-tabs ds-tabs--${this.orientation}`, role: "tablist", "aria-label": this.label, "aria-orientation": this.orientation === "vertical" ? "vertical" : undefined }, items.map((item) => {
|
|
119
|
+
const isActive = item.id === this.active;
|
|
120
|
+
return (h("button", { key: item.id, type: "button", role: "tab", id: item.id, class: `ds-tab${isActive ? " ds-tab--active" : ""}`, "aria-selected": isActive ? "true" : "false", "aria-controls": item.controls ?? item.id, tabindex: isActive ? 0 : -1, disabled: item.disabled, ref: (el) => {
|
|
121
|
+
if (el)
|
|
122
|
+
this.tabRefs.set(item.id, el);
|
|
123
|
+
else
|
|
124
|
+
this.tabRefs.delete(item.id);
|
|
125
|
+
}, onClick: () => this.onClick(item.id), onKeyDown: (e) => this.onKeyDown(e, item.id) }, item.label));
|
|
126
|
+
}))));
|
|
127
|
+
}
|
|
128
|
+
static get watchers() { return {
|
|
129
|
+
"items": [{
|
|
130
|
+
"watchItems": 0
|
|
131
|
+
}]
|
|
132
|
+
}; }
|
|
133
|
+
};
|
|
134
|
+
DsTabs.style = dsTabsCss();
|
|
135
|
+
|
|
136
|
+
export { DsTabs as ds_tabs };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { r as registerInstance, c as createEvent, h, H as Host } from './index-e-glNjK6.js';
|
|
2
|
+
|
|
3
|
+
const dsTextareaCss = () => `:host{display:inline-block;width:100%}.ds-textarea{font-family:var(--ds-font-family);font-size:var(--ds-font-size-md);background-color:transparent;color:inherit;border:1px solid color-mix(in oklch, currentColor 22%, transparent);-webkit-appearance:none;appearance:none;box-sizing:border-box;width:100%;resize:vertical}.ds-textarea::placeholder{color:color-mix(in oklch, currentColor 45%, transparent)}.ds-textarea:disabled{opacity:0.5;cursor:not-allowed;resize:none}.ds-textarea[aria-invalid="true"]{border-color:color-mix(in oklch, currentColor 55%, transparent)}.ds-textarea--md{border-radius:var(--ds-radius-sm);padding:var(--ds-spacing-sm) var(--ds-spacing-lg)}.ds-textarea--lg{border-radius:var(--ds-radius-md);padding:var(--ds-spacing-md) var(--ds-spacing-xl)}`;
|
|
4
|
+
|
|
5
|
+
const DsTextarea = class {
|
|
6
|
+
constructor(hostRef) {
|
|
7
|
+
registerInstance(this, hostRef);
|
|
8
|
+
this.dsInput = createEvent(this, "dsInput");
|
|
9
|
+
}
|
|
10
|
+
textareaEl;
|
|
11
|
+
value;
|
|
12
|
+
name;
|
|
13
|
+
placeholder;
|
|
14
|
+
disabled = false;
|
|
15
|
+
required = false;
|
|
16
|
+
invalid = false;
|
|
17
|
+
describedBy;
|
|
18
|
+
label;
|
|
19
|
+
rows = 3;
|
|
20
|
+
size = "md";
|
|
21
|
+
dsInput;
|
|
22
|
+
watchValue(newValue) {
|
|
23
|
+
if (this.textareaEl && this.textareaEl.value !== (newValue ?? "")) {
|
|
24
|
+
this.textareaEl.value = newValue ?? "";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
componentDidLoad() {
|
|
28
|
+
if (this.textareaEl && this.value !== undefined) {
|
|
29
|
+
this.textareaEl.value = this.value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
onInput = (event) => {
|
|
33
|
+
const value = event.target.value;
|
|
34
|
+
this.dsInput.emit(value);
|
|
35
|
+
};
|
|
36
|
+
render() {
|
|
37
|
+
const cls = `ds-textarea ds-textarea--${this.size}`;
|
|
38
|
+
return (h(Host, { key: '89e63314bca9f6f1b70f36c540a3ac48eed7311c' }, h("textarea", { key: '59f9b8ee9a48a4e495da7fb56745102a1f0ee7c2', class: cls, name: this.name, placeholder: this.placeholder, disabled: this.disabled, rows: this.rows, "aria-label": this.label, "aria-required": this.required ? "true" : undefined, "aria-invalid": this.invalid ? "true" : undefined, "aria-describedby": this.describedBy, ref: (el) => (this.textareaEl = el), onInput: this.onInput })));
|
|
39
|
+
}
|
|
40
|
+
static get watchers() { return {
|
|
41
|
+
"value": [{
|
|
42
|
+
"watchValue": 0
|
|
43
|
+
}]
|
|
44
|
+
}; }
|
|
45
|
+
};
|
|
46
|
+
DsTextarea.style = dsTextareaCss();
|
|
47
|
+
|
|
48
|
+
export { DsTextarea as ds_textarea };
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { r as registerInstance, a as getElement, h, H as Host } from './index-e-glNjK6.js';
|
|
2
|
+
|
|
3
|
+
const dsTooltipCss = () => `ds-tooltip{display:inline-block;position:relative;color:inherit}.ds-tooltip{position:absolute;left:50%;transform:translateX(-50%);z-index:1;max-width:240px;padding:var(--ds-spacing-xs) var(--ds-spacing-sm);font-family:var(--ds-font-family);font-size:var(--ds-font-size-md);line-height:1.3;color:inherit;background-color:Canvas;border:1px solid color-mix(in oklch, currentColor 25%, transparent);border-radius:var(--ds-radius-sm);pointer-events:none;white-space:normal}.ds-tooltip--bottom{top:calc(100% + var(--ds-spacing-xs))}.ds-tooltip--top{bottom:calc(100% + var(--ds-spacing-xs))}`;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ## Accessibility
|
|
7
|
+
*
|
|
8
|
+
* **Category:** state-machine (Tier 2)
|
|
9
|
+
* **APG pattern:** https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/
|
|
10
|
+
*
|
|
11
|
+
* Scratch build (there is no native tooltip element). Per the a11y
|
|
12
|
+
* spec §D ceremony: APG citation above, full keyboard enumeration
|
|
13
|
+
* and ARIA below, focus lifecycle in the tsx and spec.json, manual
|
|
14
|
+
* VoiceOver evidence pending (see commit message TODO).
|
|
15
|
+
*
|
|
16
|
+
* **Scope choice — wraps the consumer's first Element child as the
|
|
17
|
+
* trigger.** `<ds-tooltip label="Delete"><ds-button>×</ds-button>`
|
|
18
|
+
* — the first Element inside the ds-tooltip host is treated as the
|
|
19
|
+
* trigger. shadow:false so we can imperatively set `aria-describedby`
|
|
20
|
+
* on that light-DOM child (shadow-scoped ids can't be referenced
|
|
21
|
+
* from a light-DOM `aria-describedby`).
|
|
22
|
+
*
|
|
23
|
+
* **Consumer contract:** the first Element child MUST be focusable
|
|
24
|
+
* (`<button>`, `<a href>`, native form control, or an element with
|
|
25
|
+
* `tabindex="0"`) — otherwise keyboard users cannot summon the
|
|
26
|
+
* tooltip and the pattern fails WCAG 1.4.13. The component does not
|
|
27
|
+
* force focusability onto arbitrary children; that would produce
|
|
28
|
+
* silent AT surprises. The tooltip's `label` prop is required — a
|
|
29
|
+
* tooltip with no accessible text is inert.
|
|
30
|
+
*
|
|
31
|
+
* **Keyboard interactions (full APG enumeration):**
|
|
32
|
+
* - Focus (Tab into trigger): tooltip opens immediately (no
|
|
33
|
+
* `openDelay`). Delay-on-open is a mouse-hover ergonomic, not a
|
|
34
|
+
* keyboard one — waiting for keyboard users to see supplementary
|
|
35
|
+
* info is friction, not smoothing.
|
|
36
|
+
* - Escape (anywhere inside the tooltip's host, tooltip open):
|
|
37
|
+
* dismisses the tooltip WITHOUT moving focus. The trigger remains
|
|
38
|
+
* focused so the user can Tab away or re-summon. This is the WCAG
|
|
39
|
+
* 1.4.13 "dismissable" requirement.
|
|
40
|
+
* - Blur (Tab out of the trigger): tooltip closes immediately.
|
|
41
|
+
* - Enter / Space / arrow / printable: forwarded to the trigger
|
|
42
|
+
* (the tooltip does not intercept them). If Enter activates the
|
|
43
|
+
* underlying `<button>`, the button click closes the tooltip via
|
|
44
|
+
* the natural blur-then-refocus cycle.
|
|
45
|
+
*
|
|
46
|
+
* **Pointer interactions:**
|
|
47
|
+
* - mouseenter on the host → start `openDelay` timer → show.
|
|
48
|
+
* - mouseleave on the host → start `closeDelay` timer → hide.
|
|
49
|
+
* - Re-entering during the closeDelay window cancels the hide (the
|
|
50
|
+
* tooltip is "hoverable" — WCAG 1.4.13 — so users can move the
|
|
51
|
+
* pointer INTO the tooltip surface without it disappearing).
|
|
52
|
+
*
|
|
53
|
+
* **Persistence** (WCAG 1.4.13): the tooltip stays visible while
|
|
54
|
+
* the trigger is focused or hovered, and while the pointer is over
|
|
55
|
+
* the tooltip content itself. Dismissal only happens via Escape,
|
|
56
|
+
* blur, or genuine mouseleave (no residual hover on host or
|
|
57
|
+
* tooltip content).
|
|
58
|
+
*
|
|
59
|
+
* **ARIA attributes and value triggers:**
|
|
60
|
+
* - Tooltip element: `role="tooltip"`, `id` (auto-generated per
|
|
61
|
+
* instance so multiple tooltips on a page do not collide).
|
|
62
|
+
* - Trigger (the first Element child): `aria-describedby={tooltipId}`
|
|
63
|
+
* set on componentDidLoad and refreshed on slot mutations. We use
|
|
64
|
+
* describedby (APG default), NOT labelledby — the trigger has its
|
|
65
|
+
* own accessible name (its text or aria-label), and the tooltip
|
|
66
|
+
* supplements it.
|
|
67
|
+
*
|
|
68
|
+
* **Focus lifecycle:**
|
|
69
|
+
* - On mount: no auto-focus.
|
|
70
|
+
* - On trigger focus: tooltip opens immediately.
|
|
71
|
+
* - On trigger blur: tooltip closes.
|
|
72
|
+
* - On Escape while open: tooltip closes, focus stays on trigger.
|
|
73
|
+
* - The tooltip surface itself is NOT focusable — it is
|
|
74
|
+
* supplementary content, not an interactive region.
|
|
75
|
+
*
|
|
76
|
+
* **Motion:**
|
|
77
|
+
* - Animations: none applied by the component. Consumers who want a
|
|
78
|
+
* fade can style `[data-open="true"]` transitions gated on
|
|
79
|
+
* `prefers-reduced-motion: no-preference`.
|
|
80
|
+
*
|
|
81
|
+
* **Contrast:**
|
|
82
|
+
* - Tooltip surface: solid background via `Canvas` system color
|
|
83
|
+
* with `currentColor` text so it inherits the consumer theme's
|
|
84
|
+
* text color. Border uses `color-mix(currentColor 25%,
|
|
85
|
+
* transparent)`. Consumer theme owns the 4.5:1 text contrast and
|
|
86
|
+
* 3:1 non-text.
|
|
87
|
+
*
|
|
88
|
+
* **Screen reader announcement:**
|
|
89
|
+
* - Rendered state (closed): tooltip content is `hidden`, not in
|
|
90
|
+
* the a11y tree.
|
|
91
|
+
* - On focus: because the trigger has `aria-describedby`, AT reads
|
|
92
|
+
* the trigger's accessible name plus the tooltip text as a
|
|
93
|
+
* description. AT usually announces "…, [description]" after the
|
|
94
|
+
* trigger's role.
|
|
95
|
+
* - On Escape: the tooltip vanishes silently; AT falls back to
|
|
96
|
+
* announcing the trigger without its description on next
|
|
97
|
+
* interaction. This is APG behavior for dismissed tooltips.
|
|
98
|
+
*
|
|
99
|
+
* **Pending manual verification (see commit message TODO):**
|
|
100
|
+
* - VoiceOver on macOS: confirm the tooltip text is announced as a
|
|
101
|
+
* description of the trigger when the trigger gains focus.
|
|
102
|
+
* - VoiceOver: confirm Escape dismisses the tooltip and leaves the
|
|
103
|
+
* trigger focused; confirm re-focusing brings it back.
|
|
104
|
+
* - Chromium reduced-motion: confirm no residual animation is added
|
|
105
|
+
* by the component.
|
|
106
|
+
*/
|
|
107
|
+
let tooltipIdCounter = 0;
|
|
108
|
+
const DsTooltip = class {
|
|
109
|
+
constructor(hostRef) {
|
|
110
|
+
registerInstance(this, hostRef);
|
|
111
|
+
}
|
|
112
|
+
get hostEl() { return getElement(this); }
|
|
113
|
+
label;
|
|
114
|
+
openDelay = 250;
|
|
115
|
+
closeDelay = 100;
|
|
116
|
+
placement = "bottom";
|
|
117
|
+
tooltipId = `ds-tooltip-${++tooltipIdCounter}`;
|
|
118
|
+
tooltipEl;
|
|
119
|
+
openTimer;
|
|
120
|
+
closeTimer;
|
|
121
|
+
isOpen = false;
|
|
122
|
+
watchLabel() {
|
|
123
|
+
if (this.tooltipEl) {
|
|
124
|
+
this.tooltipEl.textContent = this.label;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
componentDidLoad() {
|
|
128
|
+
this.wireTrigger();
|
|
129
|
+
this.hostEl.addEventListener("mouseenter", this.onMouseEnter);
|
|
130
|
+
this.hostEl.addEventListener("mouseleave", this.onMouseLeave);
|
|
131
|
+
this.hostEl.addEventListener("focusin", this.onFocusIn);
|
|
132
|
+
this.hostEl.addEventListener("focusout", this.onFocusOut);
|
|
133
|
+
this.hostEl.addEventListener("keydown", this.onKeyDown);
|
|
134
|
+
}
|
|
135
|
+
disconnectedCallback() {
|
|
136
|
+
this.clearTimers();
|
|
137
|
+
this.hostEl.removeEventListener("mouseenter", this.onMouseEnter);
|
|
138
|
+
this.hostEl.removeEventListener("mouseleave", this.onMouseLeave);
|
|
139
|
+
this.hostEl.removeEventListener("focusin", this.onFocusIn);
|
|
140
|
+
this.hostEl.removeEventListener("focusout", this.onFocusOut);
|
|
141
|
+
this.hostEl.removeEventListener("keydown", this.onKeyDown);
|
|
142
|
+
}
|
|
143
|
+
wireTrigger() {
|
|
144
|
+
// First Element child within the host, ignoring our own tooltip
|
|
145
|
+
// <span> which we render at the end.
|
|
146
|
+
const children = Array.from(this.hostEl.children).filter((el) => el !== this.tooltipEl);
|
|
147
|
+
const trigger = children[0];
|
|
148
|
+
if (!trigger)
|
|
149
|
+
return;
|
|
150
|
+
trigger.setAttribute("aria-describedby", this.tooltipId);
|
|
151
|
+
}
|
|
152
|
+
clearTimers() {
|
|
153
|
+
if (this.openTimer !== undefined) {
|
|
154
|
+
window.clearTimeout(this.openTimer);
|
|
155
|
+
this.openTimer = undefined;
|
|
156
|
+
}
|
|
157
|
+
if (this.closeTimer !== undefined) {
|
|
158
|
+
window.clearTimeout(this.closeTimer);
|
|
159
|
+
this.closeTimer = undefined;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
show(delay = 0) {
|
|
163
|
+
if (this.closeTimer !== undefined) {
|
|
164
|
+
window.clearTimeout(this.closeTimer);
|
|
165
|
+
this.closeTimer = undefined;
|
|
166
|
+
}
|
|
167
|
+
if (this.isOpen)
|
|
168
|
+
return;
|
|
169
|
+
if (delay === 0) {
|
|
170
|
+
this.applyOpen(true);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (this.openTimer !== undefined)
|
|
174
|
+
return;
|
|
175
|
+
this.openTimer = window.setTimeout(() => {
|
|
176
|
+
this.openTimer = undefined;
|
|
177
|
+
this.applyOpen(true);
|
|
178
|
+
}, delay);
|
|
179
|
+
}
|
|
180
|
+
hide(delay = 0) {
|
|
181
|
+
if (this.openTimer !== undefined) {
|
|
182
|
+
window.clearTimeout(this.openTimer);
|
|
183
|
+
this.openTimer = undefined;
|
|
184
|
+
}
|
|
185
|
+
if (!this.isOpen)
|
|
186
|
+
return;
|
|
187
|
+
if (delay === 0) {
|
|
188
|
+
this.applyOpen(false);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (this.closeTimer !== undefined)
|
|
192
|
+
return;
|
|
193
|
+
this.closeTimer = window.setTimeout(() => {
|
|
194
|
+
this.closeTimer = undefined;
|
|
195
|
+
this.applyOpen(false);
|
|
196
|
+
}, delay);
|
|
197
|
+
}
|
|
198
|
+
applyOpen(open) {
|
|
199
|
+
this.isOpen = open;
|
|
200
|
+
if (!this.tooltipEl)
|
|
201
|
+
return;
|
|
202
|
+
if (open) {
|
|
203
|
+
this.tooltipEl.removeAttribute("hidden");
|
|
204
|
+
this.tooltipEl.dataset.open = "true";
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
this.tooltipEl.setAttribute("hidden", "");
|
|
208
|
+
this.tooltipEl.dataset.open = "false";
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
onMouseEnter = () => this.show(this.openDelay);
|
|
212
|
+
onMouseLeave = () => this.hide(this.closeDelay);
|
|
213
|
+
onFocusIn = () => this.show(0);
|
|
214
|
+
onFocusOut = () => this.hide(0);
|
|
215
|
+
onKeyDown = (event) => {
|
|
216
|
+
if (event.key === "Escape" && this.isOpen) {
|
|
217
|
+
this.hide(0);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
render() {
|
|
221
|
+
return (h(Host, { key: 'e3822251c9edab36fdffa9178473823f3e9ae525' }, h("slot", { key: 'f7c490f42ae09a739d8d91d32c2394dbdef03aa5' }), h("span", { key: '8bda1876026129859665f82b1983065df602d092', class: `ds-tooltip ds-tooltip--${this.placement}`, role: "tooltip", id: this.tooltipId, hidden: true, "data-open": "false", ref: (el) => (this.tooltipEl = el) }, this.label)));
|
|
222
|
+
}
|
|
223
|
+
static get watchers() { return {
|
|
224
|
+
"label": [{
|
|
225
|
+
"watchLabel": 0
|
|
226
|
+
}]
|
|
227
|
+
}; }
|
|
228
|
+
};
|
|
229
|
+
DsTooltip.style = dsTooltipCss();
|
|
230
|
+
|
|
231
|
+
export { DsTooltip as ds_tooltip };
|