@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,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 @@
|
|
|
1
|
+
export * from "./components";
|