@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.
Files changed (146) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -0
  3. package/dist/chiralkit-ds/chiralkit-ds.css +1 -0
  4. package/dist/chiralkit-ds/chiralkit-ds.esm.js +1 -0
  5. package/dist/chiralkit-ds/index.esm.js +1 -0
  6. package/dist/chiralkit-ds/p-038fa008.entry.js +1 -0
  7. package/dist/chiralkit-ds/p-2fdc1012.entry.js +1 -0
  8. package/dist/chiralkit-ds/p-306ec33b.entry.js +1 -0
  9. package/dist/chiralkit-ds/p-3f704698.entry.js +1 -0
  10. package/dist/chiralkit-ds/p-41081d22.entry.js +1 -0
  11. package/dist/chiralkit-ds/p-7d7e5aac.entry.js +1 -0
  12. package/dist/chiralkit-ds/p-84b801f8.entry.js +1 -0
  13. package/dist/chiralkit-ds/p-a28c71b4.entry.js +1 -0
  14. package/dist/chiralkit-ds/p-be84312f.entry.js +1 -0
  15. package/dist/chiralkit-ds/p-c8015fe6.entry.js +1 -0
  16. package/dist/chiralkit-ds/p-d36a6bea.entry.js +1 -0
  17. package/dist/chiralkit-ds/p-e-glNjK6.js +2 -0
  18. package/dist/chiralkit-ds/p-f0f9ff61.entry.js +1 -0
  19. package/dist/cjs/chiralkit-ds.cjs.js +24 -0
  20. package/dist/cjs/ds-badge.cjs.entry.js +18 -0
  21. package/dist/cjs/ds-button.cjs.entry.js +26 -0
  22. package/dist/cjs/ds-card.cjs.entry.js +17 -0
  23. package/dist/cjs/ds-checkbox.cjs.entry.js +67 -0
  24. package/dist/cjs/ds-code-block.cjs.entry.js +39 -0
  25. package/dist/cjs/ds-dialog.cjs.entry.js +67 -0
  26. package/dist/cjs/ds-input.cjs.entry.js +50 -0
  27. package/dist/cjs/ds-loader.cjs.entry.js +26 -0
  28. package/dist/cjs/ds-select.cjs.entry.js +85 -0
  29. package/dist/cjs/ds-tabs.cjs.entry.js +138 -0
  30. package/dist/cjs/ds-textarea.cjs.entry.js +50 -0
  31. package/dist/cjs/ds-tooltip.cjs.entry.js +233 -0
  32. package/dist/cjs/index-mMvyq7U_.js +2018 -0
  33. package/dist/cjs/index.cjs.js +10 -0
  34. package/dist/cjs/loader.cjs.js +12 -0
  35. package/dist/collection/collection-manifest.json +24 -0
  36. package/dist/collection/components/ds-badge/ds-badge.css +36 -0
  37. package/dist/collection/components/ds-badge/ds-badge.js +93 -0
  38. package/dist/collection/components/ds-button/ds-button.css +66 -0
  39. package/dist/collection/components/ds-button/ds-button.js +197 -0
  40. package/dist/collection/components/ds-card/ds-card.css +12 -0
  41. package/dist/collection/components/ds-card/ds-card.js +68 -0
  42. package/dist/collection/components/ds-checkbox/ds-checkbox.css +22 -0
  43. package/dist/collection/components/ds-checkbox/ds-checkbox.js +339 -0
  44. package/dist/collection/components/ds-code-block/ds-code-block.css +65 -0
  45. package/dist/collection/components/ds-code-block/ds-code-block.js +162 -0
  46. package/dist/collection/components/ds-dialog/ds-dialog.css +30 -0
  47. package/dist/collection/components/ds-dialog/ds-dialog.js +313 -0
  48. package/dist/collection/components/ds-input/ds-input.css +38 -0
  49. package/dist/collection/components/ds-input/ds-input.js +353 -0
  50. package/dist/collection/components/ds-loader/ds-loader.css +94 -0
  51. package/dist/collection/components/ds-loader/ds-loader.js +149 -0
  52. package/dist/collection/components/ds-select/ds-select.css +69 -0
  53. package/dist/collection/components/ds-select/ds-select.js +235 -0
  54. package/dist/collection/components/ds-tabs/ds-tabs.css +59 -0
  55. package/dist/collection/components/ds-tabs/ds-tabs.js +248 -0
  56. package/dist/collection/components/ds-textarea/ds-textarea.css +41 -0
  57. package/dist/collection/components/ds-textarea/ds-textarea.js +342 -0
  58. package/dist/collection/components/ds-tooltip/ds-tooltip.css +31 -0
  59. package/dist/collection/components/ds-tooltip/ds-tooltip.js +318 -0
  60. package/dist/collection/components.js +2 -0
  61. package/dist/collection/index.js +1 -0
  62. package/dist/components/ds-badge.d.ts +11 -0
  63. package/dist/components/ds-badge.js +1 -0
  64. package/dist/components/ds-button.d.ts +11 -0
  65. package/dist/components/ds-button.js +1 -0
  66. package/dist/components/ds-card.d.ts +11 -0
  67. package/dist/components/ds-card.js +1 -0
  68. package/dist/components/ds-checkbox.d.ts +11 -0
  69. package/dist/components/ds-checkbox.js +1 -0
  70. package/dist/components/ds-code-block.d.ts +11 -0
  71. package/dist/components/ds-code-block.js +1 -0
  72. package/dist/components/ds-dialog.d.ts +11 -0
  73. package/dist/components/ds-dialog.js +1 -0
  74. package/dist/components/ds-input.d.ts +11 -0
  75. package/dist/components/ds-input.js +1 -0
  76. package/dist/components/ds-loader.d.ts +11 -0
  77. package/dist/components/ds-loader.js +1 -0
  78. package/dist/components/ds-select.d.ts +11 -0
  79. package/dist/components/ds-select.js +1 -0
  80. package/dist/components/ds-tabs.d.ts +11 -0
  81. package/dist/components/ds-tabs.js +1 -0
  82. package/dist/components/ds-textarea.d.ts +11 -0
  83. package/dist/components/ds-textarea.js +1 -0
  84. package/dist/components/ds-tooltip.d.ts +11 -0
  85. package/dist/components/ds-tooltip.js +1 -0
  86. package/dist/components/index.d.ts +35 -0
  87. package/dist/components/index.js +1 -0
  88. package/dist/components/p-DDUZow8F.js +1 -0
  89. package/dist/components/p-ZsYP0VBq.js +1 -0
  90. package/dist/components/p-k0ywxWpC.js +1 -0
  91. package/dist/esm/chiralkit-ds.js +20 -0
  92. package/dist/esm/ds-badge.entry.js +16 -0
  93. package/dist/esm/ds-button.entry.js +24 -0
  94. package/dist/esm/ds-card.entry.js +15 -0
  95. package/dist/esm/ds-checkbox.entry.js +65 -0
  96. package/dist/esm/ds-code-block.entry.js +37 -0
  97. package/dist/esm/ds-dialog.entry.js +65 -0
  98. package/dist/esm/ds-input.entry.js +48 -0
  99. package/dist/esm/ds-loader.entry.js +24 -0
  100. package/dist/esm/ds-select.entry.js +83 -0
  101. package/dist/esm/ds-tabs.entry.js +136 -0
  102. package/dist/esm/ds-textarea.entry.js +48 -0
  103. package/dist/esm/ds-tooltip.entry.js +231 -0
  104. package/dist/esm/index-e-glNjK6.js +2008 -0
  105. package/dist/esm/index.js +3 -0
  106. package/dist/esm/loader.js +10 -0
  107. package/dist/index.cjs.js +1 -0
  108. package/dist/index.js +1 -0
  109. package/dist/layouts/app-shell.css +84 -0
  110. package/dist/tests/a11y.js +55 -0
  111. package/dist/types/Users/alex/projects/MyGitHub/chiralkit/packages/design-system/.stencil/tests/a11y.d.ts +17 -0
  112. package/dist/types/components/ds-badge/ds-badge.d.ts +54 -0
  113. package/dist/types/components/ds-button/ds-button.d.ts +75 -0
  114. package/dist/types/components/ds-card/ds-card.d.ts +53 -0
  115. package/dist/types/components/ds-checkbox/ds-checkbox.d.ts +94 -0
  116. package/dist/types/components/ds-code-block/ds-code-block.d.ts +83 -0
  117. package/dist/types/components/ds-dialog/ds-dialog.d.ts +148 -0
  118. package/dist/types/components/ds-input/ds-input.d.ts +104 -0
  119. package/dist/types/components/ds-loader/ds-loader.d.ts +64 -0
  120. package/dist/types/components/ds-select/ds-select.d.ts +123 -0
  121. package/dist/types/components/ds-tabs/ds-tabs.d.ts +155 -0
  122. package/dist/types/components/ds-textarea/ds-textarea.d.ts +93 -0
  123. package/dist/types/components/ds-tooltip/ds-tooltip.d.ts +26 -0
  124. package/dist/types/components.d.ts +3068 -0
  125. package/dist/types/index.d.ts +1 -0
  126. package/dist/types/stencil-public-runtime.d.ts +1861 -0
  127. package/docs.json +2112 -0
  128. package/loader/cdn.js +1 -0
  129. package/loader/index.cjs.js +1 -0
  130. package/loader/index.d.ts +24 -0
  131. package/loader/index.es2017.js +1 -0
  132. package/loader/index.js +2 -0
  133. package/package.json +78 -0
  134. package/react/components/ds-badge.tsx +25 -0
  135. package/react/components/ds-button.tsx +25 -0
  136. package/react/components/ds-card.tsx +25 -0
  137. package/react/components/ds-checkbox.tsx +25 -0
  138. package/react/components/ds-code-block.tsx +25 -0
  139. package/react/components/ds-dialog.tsx +31 -0
  140. package/react/components/ds-input.tsx +25 -0
  141. package/react/components/ds-loader.tsx +25 -0
  142. package/react/components/ds-select.tsx +25 -0
  143. package/react/components/ds-tabs.tsx +25 -0
  144. package/react/components/ds-textarea.tsx +25 -0
  145. package/react/components/ds-tooltip.tsx +25 -0
  146. package/react/components.ts +162 -0
@@ -0,0 +1,342 @@
1
+ import { h, Host, } from "@stencil/core";
2
+ /**
3
+ * ## Accessibility
4
+ *
5
+ * **Category:** form-field
6
+ * **APG pattern:** N/A — native element. This wraps a native
7
+ * `<textarea>`; there is no WAI-ARIA APG pattern for a plain
8
+ * multi-line text field because the browser already implements it
9
+ * natively. Reference:
10
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea
11
+ *
12
+ * **Keyboard interactions:**
13
+ * - Tab: moves focus to the native `<textarea>`; Tab inside the
14
+ * textarea inserts a tab character only when the browser is
15
+ * configured to do so (default behavior: Tab moves focus out).
16
+ * - Printable characters: insert at the caret position (native).
17
+ * - Enter: inserts a newline within the value (native), does not
18
+ * submit the enclosing form (unlike a single-line input).
19
+ * - Backspace / Delete: native editing.
20
+ * - Arrow keys: move the caret within the value (native).
21
+ * - Escape: no native meaning for a text area.
22
+ *
23
+ * **ARIA:**
24
+ * - Roles used: none explicit — the native `<textarea>` supplies
25
+ * the implicit `textbox` role with multiline behavior.
26
+ * - State attributes: `disabled` (native attribute, not
27
+ * `aria-disabled`), `aria-invalid` (from the `invalid` prop),
28
+ * `aria-required` (from the `required` prop), `aria-describedby`
29
+ * (from the `describedBy` prop, expected to reference a light-DOM
30
+ * node id — typically a hint or error message rendered by the
31
+ * consumer alongside the field).
32
+ * - Labeling strategy: `aria-label` set from the `label` prop onto
33
+ * the internal `<textarea>`. This is the **canonical, recommended**
34
+ * mode. Wrapping the host in a light-DOM `<label>` also produces
35
+ * an accessible name in Chromium via flattened-tree resolution —
36
+ * same secondary mode documented on `ds-input` and `ds-select`,
37
+ * verified in this component's e2e tests. Setting `aria-label`
38
+ * directly on the `<ds-textarea>` host does NOT work: shadow-DOM
39
+ * attribute forwarding would be required — use the `label` prop.
40
+ *
41
+ * **Focus:**
42
+ * - On mount: nothing auto-focuses.
43
+ * - On value change: focus remains on the native `<textarea>`.
44
+ * - Focus indicator: the browser default `:focus-visible` ring is
45
+ * kept on purpose — a themed focus ring using `--ds-color-focus`
46
+ * is a future decision once the DS ships a shared focus utility.
47
+ *
48
+ * **Motion:**
49
+ * - Animations: none.
50
+ * - Reduced-motion behavior: N/A — no animation to collapse.
51
+ *
52
+ * **Contrast:**
53
+ * - Text: inherits `color: inherit` / `currentColor`; ratio depends
54
+ * on the consumer's theme, not verified in isolation.
55
+ * - Border: `color-mix(in oklch, currentColor 22%, transparent)`,
56
+ * matching `ds-input`.
57
+ *
58
+ * **Screen reader announcement:**
59
+ * - Rendered state: announces the `label` prop text as the
60
+ * accessible name, followed by "edit text, multiline" (browser/AT
61
+ * wording varies). When `required=true` the announcement includes
62
+ * "required"; when `invalid=true` it includes "invalid".
63
+ * - State change: on value edit, AT typically echoes the typed
64
+ * character (native behavior); no `aria-live` added.
65
+ *
66
+ * **Consumer contract:**
67
+ * - `placeholder` is a hint, not a label — always pair with `label`.
68
+ * - When `invalid=true`, pair with a `describedBy` id that points at
69
+ * a visible error message; `aria-invalid` alone gives the AT the
70
+ * status but not the reason.
71
+ * - `rows` sets the initial visible line count. Vertical resize is
72
+ * left to the native default (`resize: vertical`) so users can
73
+ * grow the field for long entries; the consumer's CSS can pin it
74
+ * with `resize: none` when a fixed height matters.
75
+ */
76
+ export class DsTextarea {
77
+ textareaEl;
78
+ value;
79
+ name;
80
+ placeholder;
81
+ disabled = false;
82
+ required = false;
83
+ invalid = false;
84
+ describedBy;
85
+ label;
86
+ rows = 3;
87
+ size = "md";
88
+ dsInput;
89
+ watchValue(newValue) {
90
+ if (this.textareaEl && this.textareaEl.value !== (newValue ?? "")) {
91
+ this.textareaEl.value = newValue ?? "";
92
+ }
93
+ }
94
+ componentDidLoad() {
95
+ if (this.textareaEl && this.value !== undefined) {
96
+ this.textareaEl.value = this.value;
97
+ }
98
+ }
99
+ onInput = (event) => {
100
+ const value = event.target.value;
101
+ this.dsInput.emit(value);
102
+ };
103
+ render() {
104
+ const cls = `ds-textarea ds-textarea--${this.size}`;
105
+ 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 })));
106
+ }
107
+ static get is() { return "ds-textarea"; }
108
+ static get encapsulation() { return "shadow"; }
109
+ static get originalStyleUrls() {
110
+ return {
111
+ "$": ["ds-textarea.css"]
112
+ };
113
+ }
114
+ static get styleUrls() {
115
+ return {
116
+ "$": ["ds-textarea.css"]
117
+ };
118
+ }
119
+ static get properties() {
120
+ return {
121
+ "value": {
122
+ "type": "string",
123
+ "mutable": false,
124
+ "complexType": {
125
+ "original": "string",
126
+ "resolved": "string | undefined",
127
+ "references": {}
128
+ },
129
+ "required": false,
130
+ "optional": true,
131
+ "docs": {
132
+ "tags": [],
133
+ "text": ""
134
+ },
135
+ "getter": false,
136
+ "setter": false,
137
+ "reflect": false,
138
+ "attribute": "value"
139
+ },
140
+ "name": {
141
+ "type": "string",
142
+ "mutable": false,
143
+ "complexType": {
144
+ "original": "string",
145
+ "resolved": "string | undefined",
146
+ "references": {}
147
+ },
148
+ "required": false,
149
+ "optional": true,
150
+ "docs": {
151
+ "tags": [],
152
+ "text": ""
153
+ },
154
+ "getter": false,
155
+ "setter": false,
156
+ "reflect": false,
157
+ "attribute": "name"
158
+ },
159
+ "placeholder": {
160
+ "type": "string",
161
+ "mutable": false,
162
+ "complexType": {
163
+ "original": "string",
164
+ "resolved": "string | undefined",
165
+ "references": {}
166
+ },
167
+ "required": false,
168
+ "optional": true,
169
+ "docs": {
170
+ "tags": [],
171
+ "text": ""
172
+ },
173
+ "getter": false,
174
+ "setter": false,
175
+ "reflect": false,
176
+ "attribute": "placeholder"
177
+ },
178
+ "disabled": {
179
+ "type": "boolean",
180
+ "mutable": false,
181
+ "complexType": {
182
+ "original": "boolean",
183
+ "resolved": "boolean",
184
+ "references": {}
185
+ },
186
+ "required": false,
187
+ "optional": false,
188
+ "docs": {
189
+ "tags": [],
190
+ "text": ""
191
+ },
192
+ "getter": false,
193
+ "setter": false,
194
+ "reflect": false,
195
+ "attribute": "disabled",
196
+ "defaultValue": "false"
197
+ },
198
+ "required": {
199
+ "type": "boolean",
200
+ "mutable": false,
201
+ "complexType": {
202
+ "original": "boolean",
203
+ "resolved": "boolean",
204
+ "references": {}
205
+ },
206
+ "required": false,
207
+ "optional": false,
208
+ "docs": {
209
+ "tags": [],
210
+ "text": ""
211
+ },
212
+ "getter": false,
213
+ "setter": false,
214
+ "reflect": false,
215
+ "attribute": "required",
216
+ "defaultValue": "false"
217
+ },
218
+ "invalid": {
219
+ "type": "boolean",
220
+ "mutable": false,
221
+ "complexType": {
222
+ "original": "boolean",
223
+ "resolved": "boolean",
224
+ "references": {}
225
+ },
226
+ "required": false,
227
+ "optional": false,
228
+ "docs": {
229
+ "tags": [],
230
+ "text": ""
231
+ },
232
+ "getter": false,
233
+ "setter": false,
234
+ "reflect": false,
235
+ "attribute": "invalid",
236
+ "defaultValue": "false"
237
+ },
238
+ "describedBy": {
239
+ "type": "string",
240
+ "mutable": false,
241
+ "complexType": {
242
+ "original": "string",
243
+ "resolved": "string | undefined",
244
+ "references": {}
245
+ },
246
+ "required": false,
247
+ "optional": true,
248
+ "docs": {
249
+ "tags": [],
250
+ "text": ""
251
+ },
252
+ "getter": false,
253
+ "setter": false,
254
+ "reflect": false,
255
+ "attribute": "described-by"
256
+ },
257
+ "label": {
258
+ "type": "string",
259
+ "mutable": false,
260
+ "complexType": {
261
+ "original": "string",
262
+ "resolved": "string | undefined",
263
+ "references": {}
264
+ },
265
+ "required": false,
266
+ "optional": true,
267
+ "docs": {
268
+ "tags": [],
269
+ "text": ""
270
+ },
271
+ "getter": false,
272
+ "setter": false,
273
+ "reflect": false,
274
+ "attribute": "label"
275
+ },
276
+ "rows": {
277
+ "type": "number",
278
+ "mutable": false,
279
+ "complexType": {
280
+ "original": "number",
281
+ "resolved": "number",
282
+ "references": {}
283
+ },
284
+ "required": false,
285
+ "optional": false,
286
+ "docs": {
287
+ "tags": [],
288
+ "text": ""
289
+ },
290
+ "getter": false,
291
+ "setter": false,
292
+ "reflect": false,
293
+ "attribute": "rows",
294
+ "defaultValue": "3"
295
+ },
296
+ "size": {
297
+ "type": "string",
298
+ "mutable": false,
299
+ "complexType": {
300
+ "original": "\"md\" | \"lg\"",
301
+ "resolved": "\"lg\" | \"md\"",
302
+ "references": {}
303
+ },
304
+ "required": false,
305
+ "optional": false,
306
+ "docs": {
307
+ "tags": [],
308
+ "text": ""
309
+ },
310
+ "getter": false,
311
+ "setter": false,
312
+ "reflect": false,
313
+ "attribute": "size",
314
+ "defaultValue": "\"md\""
315
+ }
316
+ };
317
+ }
318
+ static get events() {
319
+ return [{
320
+ "method": "dsInput",
321
+ "name": "dsInput",
322
+ "bubbles": true,
323
+ "cancelable": true,
324
+ "composed": true,
325
+ "docs": {
326
+ "tags": [],
327
+ "text": ""
328
+ },
329
+ "complexType": {
330
+ "original": "string",
331
+ "resolved": "string",
332
+ "references": {}
333
+ }
334
+ }];
335
+ }
336
+ static get watchers() {
337
+ return [{
338
+ "propName": "value",
339
+ "methodName": "watchValue"
340
+ }];
341
+ }
342
+ }
@@ -0,0 +1,31 @@
1
+ ds-tooltip {
2
+ display: inline-block;
3
+ position: relative;
4
+ color: inherit;
5
+ }
6
+
7
+ .ds-tooltip {
8
+ position: absolute;
9
+ left: 50%;
10
+ transform: translateX(-50%);
11
+ z-index: 1;
12
+ max-width: 240px;
13
+ padding: var(--ds-spacing-xs) var(--ds-spacing-sm);
14
+ font-family: var(--ds-font-family);
15
+ font-size: var(--ds-font-size-md);
16
+ line-height: 1.3;
17
+ color: inherit;
18
+ background-color: Canvas;
19
+ border: 1px solid color-mix(in oklch, currentColor 25%, transparent);
20
+ border-radius: var(--ds-radius-sm);
21
+ pointer-events: none;
22
+ white-space: normal;
23
+ }
24
+
25
+ .ds-tooltip--bottom {
26
+ top: calc(100% + var(--ds-spacing-xs));
27
+ }
28
+
29
+ .ds-tooltip--top {
30
+ bottom: calc(100% + var(--ds-spacing-xs));
31
+ }
@@ -0,0 +1,318 @@
1
+ import { h, Host } from "@stencil/core";
2
+ /**
3
+ * ## Accessibility
4
+ *
5
+ * **Category:** state-machine (Tier 2)
6
+ * **APG pattern:** https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/
7
+ *
8
+ * Scratch build (there is no native tooltip element). Per the a11y
9
+ * spec §D ceremony: APG citation above, full keyboard enumeration
10
+ * and ARIA below, focus lifecycle in the tsx and spec.json, manual
11
+ * VoiceOver evidence pending (see commit message TODO).
12
+ *
13
+ * **Scope choice — wraps the consumer's first Element child as the
14
+ * trigger.** `<ds-tooltip label="Delete"><ds-button>×</ds-button>`
15
+ * — the first Element inside the ds-tooltip host is treated as the
16
+ * trigger. shadow:false so we can imperatively set `aria-describedby`
17
+ * on that light-DOM child (shadow-scoped ids can't be referenced
18
+ * from a light-DOM `aria-describedby`).
19
+ *
20
+ * **Consumer contract:** the first Element child MUST be focusable
21
+ * (`<button>`, `<a href>`, native form control, or an element with
22
+ * `tabindex="0"`) — otherwise keyboard users cannot summon the
23
+ * tooltip and the pattern fails WCAG 1.4.13. The component does not
24
+ * force focusability onto arbitrary children; that would produce
25
+ * silent AT surprises. The tooltip's `label` prop is required — a
26
+ * tooltip with no accessible text is inert.
27
+ *
28
+ * **Keyboard interactions (full APG enumeration):**
29
+ * - Focus (Tab into trigger): tooltip opens immediately (no
30
+ * `openDelay`). Delay-on-open is a mouse-hover ergonomic, not a
31
+ * keyboard one — waiting for keyboard users to see supplementary
32
+ * info is friction, not smoothing.
33
+ * - Escape (anywhere inside the tooltip's host, tooltip open):
34
+ * dismisses the tooltip WITHOUT moving focus. The trigger remains
35
+ * focused so the user can Tab away or re-summon. This is the WCAG
36
+ * 1.4.13 "dismissable" requirement.
37
+ * - Blur (Tab out of the trigger): tooltip closes immediately.
38
+ * - Enter / Space / arrow / printable: forwarded to the trigger
39
+ * (the tooltip does not intercept them). If Enter activates the
40
+ * underlying `<button>`, the button click closes the tooltip via
41
+ * the natural blur-then-refocus cycle.
42
+ *
43
+ * **Pointer interactions:**
44
+ * - mouseenter on the host → start `openDelay` timer → show.
45
+ * - mouseleave on the host → start `closeDelay` timer → hide.
46
+ * - Re-entering during the closeDelay window cancels the hide (the
47
+ * tooltip is "hoverable" — WCAG 1.4.13 — so users can move the
48
+ * pointer INTO the tooltip surface without it disappearing).
49
+ *
50
+ * **Persistence** (WCAG 1.4.13): the tooltip stays visible while
51
+ * the trigger is focused or hovered, and while the pointer is over
52
+ * the tooltip content itself. Dismissal only happens via Escape,
53
+ * blur, or genuine mouseleave (no residual hover on host or
54
+ * tooltip content).
55
+ *
56
+ * **ARIA attributes and value triggers:**
57
+ * - Tooltip element: `role="tooltip"`, `id` (auto-generated per
58
+ * instance so multiple tooltips on a page do not collide).
59
+ * - Trigger (the first Element child): `aria-describedby={tooltipId}`
60
+ * set on componentDidLoad and refreshed on slot mutations. We use
61
+ * describedby (APG default), NOT labelledby — the trigger has its
62
+ * own accessible name (its text or aria-label), and the tooltip
63
+ * supplements it.
64
+ *
65
+ * **Focus lifecycle:**
66
+ * - On mount: no auto-focus.
67
+ * - On trigger focus: tooltip opens immediately.
68
+ * - On trigger blur: tooltip closes.
69
+ * - On Escape while open: tooltip closes, focus stays on trigger.
70
+ * - The tooltip surface itself is NOT focusable — it is
71
+ * supplementary content, not an interactive region.
72
+ *
73
+ * **Motion:**
74
+ * - Animations: none applied by the component. Consumers who want a
75
+ * fade can style `[data-open="true"]` transitions gated on
76
+ * `prefers-reduced-motion: no-preference`.
77
+ *
78
+ * **Contrast:**
79
+ * - Tooltip surface: solid background via `Canvas` system color
80
+ * with `currentColor` text so it inherits the consumer theme's
81
+ * text color. Border uses `color-mix(currentColor 25%,
82
+ * transparent)`. Consumer theme owns the 4.5:1 text contrast and
83
+ * 3:1 non-text.
84
+ *
85
+ * **Screen reader announcement:**
86
+ * - Rendered state (closed): tooltip content is `hidden`, not in
87
+ * the a11y tree.
88
+ * - On focus: because the trigger has `aria-describedby`, AT reads
89
+ * the trigger's accessible name plus the tooltip text as a
90
+ * description. AT usually announces "…, [description]" after the
91
+ * trigger's role.
92
+ * - On Escape: the tooltip vanishes silently; AT falls back to
93
+ * announcing the trigger without its description on next
94
+ * interaction. This is APG behavior for dismissed tooltips.
95
+ *
96
+ * **Pending manual verification (see commit message TODO):**
97
+ * - VoiceOver on macOS: confirm the tooltip text is announced as a
98
+ * description of the trigger when the trigger gains focus.
99
+ * - VoiceOver: confirm Escape dismisses the tooltip and leaves the
100
+ * trigger focused; confirm re-focusing brings it back.
101
+ * - Chromium reduced-motion: confirm no residual animation is added
102
+ * by the component.
103
+ */
104
+ let tooltipIdCounter = 0;
105
+ export class DsTooltip {
106
+ hostEl;
107
+ label;
108
+ openDelay = 250;
109
+ closeDelay = 100;
110
+ placement = "bottom";
111
+ tooltipId = `ds-tooltip-${++tooltipIdCounter}`;
112
+ tooltipEl;
113
+ openTimer;
114
+ closeTimer;
115
+ isOpen = false;
116
+ watchLabel() {
117
+ if (this.tooltipEl) {
118
+ this.tooltipEl.textContent = this.label;
119
+ }
120
+ }
121
+ componentDidLoad() {
122
+ this.wireTrigger();
123
+ this.hostEl.addEventListener("mouseenter", this.onMouseEnter);
124
+ this.hostEl.addEventListener("mouseleave", this.onMouseLeave);
125
+ this.hostEl.addEventListener("focusin", this.onFocusIn);
126
+ this.hostEl.addEventListener("focusout", this.onFocusOut);
127
+ this.hostEl.addEventListener("keydown", this.onKeyDown);
128
+ }
129
+ disconnectedCallback() {
130
+ this.clearTimers();
131
+ this.hostEl.removeEventListener("mouseenter", this.onMouseEnter);
132
+ this.hostEl.removeEventListener("mouseleave", this.onMouseLeave);
133
+ this.hostEl.removeEventListener("focusin", this.onFocusIn);
134
+ this.hostEl.removeEventListener("focusout", this.onFocusOut);
135
+ this.hostEl.removeEventListener("keydown", this.onKeyDown);
136
+ }
137
+ wireTrigger() {
138
+ // First Element child within the host, ignoring our own tooltip
139
+ // <span> which we render at the end.
140
+ const children = Array.from(this.hostEl.children).filter((el) => el !== this.tooltipEl);
141
+ const trigger = children[0];
142
+ if (!trigger)
143
+ return;
144
+ trigger.setAttribute("aria-describedby", this.tooltipId);
145
+ }
146
+ clearTimers() {
147
+ if (this.openTimer !== undefined) {
148
+ window.clearTimeout(this.openTimer);
149
+ this.openTimer = undefined;
150
+ }
151
+ if (this.closeTimer !== undefined) {
152
+ window.clearTimeout(this.closeTimer);
153
+ this.closeTimer = undefined;
154
+ }
155
+ }
156
+ show(delay = 0) {
157
+ if (this.closeTimer !== undefined) {
158
+ window.clearTimeout(this.closeTimer);
159
+ this.closeTimer = undefined;
160
+ }
161
+ if (this.isOpen)
162
+ return;
163
+ if (delay === 0) {
164
+ this.applyOpen(true);
165
+ return;
166
+ }
167
+ if (this.openTimer !== undefined)
168
+ return;
169
+ this.openTimer = window.setTimeout(() => {
170
+ this.openTimer = undefined;
171
+ this.applyOpen(true);
172
+ }, delay);
173
+ }
174
+ hide(delay = 0) {
175
+ if (this.openTimer !== undefined) {
176
+ window.clearTimeout(this.openTimer);
177
+ this.openTimer = undefined;
178
+ }
179
+ if (!this.isOpen)
180
+ return;
181
+ if (delay === 0) {
182
+ this.applyOpen(false);
183
+ return;
184
+ }
185
+ if (this.closeTimer !== undefined)
186
+ return;
187
+ this.closeTimer = window.setTimeout(() => {
188
+ this.closeTimer = undefined;
189
+ this.applyOpen(false);
190
+ }, delay);
191
+ }
192
+ applyOpen(open) {
193
+ this.isOpen = open;
194
+ if (!this.tooltipEl)
195
+ return;
196
+ if (open) {
197
+ this.tooltipEl.removeAttribute("hidden");
198
+ this.tooltipEl.dataset.open = "true";
199
+ }
200
+ else {
201
+ this.tooltipEl.setAttribute("hidden", "");
202
+ this.tooltipEl.dataset.open = "false";
203
+ }
204
+ }
205
+ onMouseEnter = () => this.show(this.openDelay);
206
+ onMouseLeave = () => this.hide(this.closeDelay);
207
+ onFocusIn = () => this.show(0);
208
+ onFocusOut = () => this.hide(0);
209
+ onKeyDown = (event) => {
210
+ if (event.key === "Escape" && this.isOpen) {
211
+ this.hide(0);
212
+ }
213
+ };
214
+ render() {
215
+ 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)));
216
+ }
217
+ static get is() { return "ds-tooltip"; }
218
+ static get originalStyleUrls() {
219
+ return {
220
+ "$": ["ds-tooltip.css"]
221
+ };
222
+ }
223
+ static get styleUrls() {
224
+ return {
225
+ "$": ["ds-tooltip.css"]
226
+ };
227
+ }
228
+ static get properties() {
229
+ return {
230
+ "label": {
231
+ "type": "string",
232
+ "mutable": false,
233
+ "complexType": {
234
+ "original": "string",
235
+ "resolved": "string",
236
+ "references": {}
237
+ },
238
+ "required": true,
239
+ "optional": false,
240
+ "docs": {
241
+ "tags": [],
242
+ "text": ""
243
+ },
244
+ "getter": false,
245
+ "setter": false,
246
+ "reflect": false,
247
+ "attribute": "label"
248
+ },
249
+ "openDelay": {
250
+ "type": "number",
251
+ "mutable": false,
252
+ "complexType": {
253
+ "original": "number",
254
+ "resolved": "number",
255
+ "references": {}
256
+ },
257
+ "required": false,
258
+ "optional": false,
259
+ "docs": {
260
+ "tags": [],
261
+ "text": ""
262
+ },
263
+ "getter": false,
264
+ "setter": false,
265
+ "reflect": false,
266
+ "attribute": "open-delay",
267
+ "defaultValue": "250"
268
+ },
269
+ "closeDelay": {
270
+ "type": "number",
271
+ "mutable": false,
272
+ "complexType": {
273
+ "original": "number",
274
+ "resolved": "number",
275
+ "references": {}
276
+ },
277
+ "required": false,
278
+ "optional": false,
279
+ "docs": {
280
+ "tags": [],
281
+ "text": ""
282
+ },
283
+ "getter": false,
284
+ "setter": false,
285
+ "reflect": false,
286
+ "attribute": "close-delay",
287
+ "defaultValue": "100"
288
+ },
289
+ "placement": {
290
+ "type": "string",
291
+ "mutable": false,
292
+ "complexType": {
293
+ "original": "\"top\" | \"bottom\"",
294
+ "resolved": "\"bottom\" | \"top\"",
295
+ "references": {}
296
+ },
297
+ "required": false,
298
+ "optional": false,
299
+ "docs": {
300
+ "tags": [],
301
+ "text": ""
302
+ },
303
+ "getter": false,
304
+ "setter": false,
305
+ "reflect": false,
306
+ "attribute": "placement",
307
+ "defaultValue": "\"bottom\""
308
+ }
309
+ };
310
+ }
311
+ static get elementRef() { return "hostEl"; }
312
+ static get watchers() {
313
+ return [{
314
+ "propName": "label",
315
+ "methodName": "watchLabel"
316
+ }];
317
+ }
318
+ }
@@ -0,0 +1,2 @@
1
+ export { DsButton } from "./components/ds-button/ds-button";
2
+ export { DsSelect } from "./components/ds-select/ds-select";
@@ -0,0 +1 @@
1
+ export * from "./components";