@inglorious/ui 8.3.1 → 8.4.1
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/package.json +9 -9
- package/src/controls/button/button.stories.js +1 -1
- package/src/controls/button/template.js +1 -1
- package/src/controls/button-group/button-group.stories.js +1 -1
- package/src/controls/button-group/template.js +1 -1
- package/src/controls/checkbox/checkbox.stories.js +1 -1
- package/src/controls/checkbox/style.css +1 -1
- package/src/controls/checkbox/template.js +7 -5
- package/src/controls/combobox/combobox.stories.js +1 -1
- package/src/controls/combobox/template.js +16 -11
- package/src/controls/fab/fab.stories.js +1 -1
- package/src/controls/file-upload/file-upload.stories.js +1 -1
- package/src/controls/file-upload/template.js +18 -7
- package/src/controls/icon-button/icon-button.stories.js +1 -1
- package/src/controls/image-cropper/image-cropper.stories.js +1 -1
- package/src/controls/input/input.stories.js +1 -1
- package/src/controls/input/template.js +16 -8
- package/src/controls/radio-group/radio-group.stories.js +1 -1
- package/src/controls/radio-group/template.js +8 -6
- package/src/controls/rating/rating.stories.js +1 -1
- package/src/controls/select/select.stories.js +1 -1
- package/src/controls/select/template.js +48 -46
- package/src/controls/slider/slider.stories.js +1 -1
- package/src/controls/slider/template.js +6 -4
- package/src/controls/switch/switch.stories.js +1 -1
- package/src/controls/switch/template.js +6 -4
- package/src/controls/textarea/template.js +17 -6
- package/src/controls/textarea/textarea.stories.js +1 -1
- package/src/data-display/avatar/avatar.stories.js +1 -1
- package/src/data-display/badge/badge.stories.js +1 -1
- package/src/data-display/before-after/before-after.stories.js +1 -1
- package/src/data-display/carousel/carousel.stories.js +10 -9
- package/src/data-display/chip/chip.stories.js +1 -1
- package/src/data-display/data-grid/data-grid.stories.js +11 -11
- package/src/data-display/divider/divider.stories.js +1 -1
- package/src/data-display/icon/icon-utf8.stories.js +1 -1
- package/src/data-display/icon/icon.stories.js +1 -1
- package/src/data-display/icon/template.js +1 -1
- package/src/data-display/list/list.stories.js +1 -1
- package/src/data-display/list/template.js +1 -1
- package/src/data-display/table/table.stories.js +1 -1
- package/src/data-display/table/template.js +1 -1
- package/src/data-display/tooltip/template.js +1 -1
- package/src/data-display/tooltip/tooltip.stories.js +1 -1
- package/src/data-display/typography/template.js +1 -1
- package/src/data-display/typography/typography.stories.js +1 -1
- package/src/data-display/virtual-list/template.js +21 -21
- package/src/examples/dashboard/before-after-data.js +17 -14
- package/src/examples/dashboard/dashboard.stories.js +1 -1
- package/src/examples/dashboard/primitive-content.js +32 -30
- package/src/examples/dashboard/theme-utils.js +5 -5
- package/src/feedback/alert/alert.stories.js +1 -1
- package/src/feedback/alert/template.js +1 -1
- package/src/feedback/backdrop/backdrop.stories.js +1 -1
- package/src/feedback/dialog/dialog.stories.js +1 -1
- package/src/feedback/progress/progress.stories.js +1 -1
- package/src/feedback/progress/template.js +4 -4
- package/src/feedback/skeleton/skeleton.stories.js +1 -1
- package/src/feedback/snackbar/snackbar.stories.js +1 -1
- package/src/layout/container/container.stories.js +1 -1
- package/src/layout/container/template.js +1 -1
- package/src/layout/flex/flex.stories.js +1 -1
- package/src/layout/flex/template.js +1 -1
- package/src/layout/grid/grid.stories.js +1 -1
- package/src/navigation/bottom-navigation/bottom-navigation.stories.js +1 -1
- package/src/navigation/bottom-navigation/template.js +1 -1
- package/src/navigation/breadcrumbs/breadcrumbs.stories.js +1 -1
- package/src/navigation/breadcrumbs/template.js +1 -1
- package/src/navigation/drawer/drawer.stories.js +1 -1
- package/src/navigation/link/link.stories.js +1 -1
- package/src/navigation/link/template.js +1 -1
- package/src/navigation/menu/menu.stories.js +1 -1
- package/src/navigation/menu/template.js +1 -1
- package/src/navigation/pagination/pagination.stories.js +1 -1
- package/src/navigation/speed-dial/speed-dial.stories.js +1 -1
- package/src/navigation/stepper/stepper.stories.js +1 -1
- package/src/navigation/tabs/tabs.stories.js +1 -1
- package/src/shared/fallback-label.js +18 -0
- package/src/surfaces/accordion/accordion.stories.js +1 -1
- package/src/surfaces/accordion/template.js +1 -1
- package/src/surfaces/app-bar/app-bar.stories.js +1 -1
- package/src/surfaces/app-bar/template.js +1 -1
- package/src/surfaces/card/card.stories.js +1 -1
- package/src/surfaces/card/template.js +1 -1
- package/src/surfaces/paper/paper.stories.js +1 -1
- package/src/surfaces/paper/template.js +1 -1
- package/src/shared/{applyElementProps.js → apply-element-props.js} +27 -27
- /package/src/stories/{notifyStory.js → notify-story.js} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/ui",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.4.1",
|
|
4
4
|
"description": "A design system for Inglorious Web with render-function components and customizable themes",
|
|
5
5
|
"author": "Matteo Antony Mistretta <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -306,21 +306,21 @@
|
|
|
306
306
|
"**/*.css"
|
|
307
307
|
],
|
|
308
308
|
"peerDependencies": {
|
|
309
|
-
"@inglorious/store": "
|
|
310
|
-
"@inglorious/utils": "5.
|
|
311
|
-
"@inglorious/web": "6.
|
|
309
|
+
"@inglorious/store": "12.0.0",
|
|
310
|
+
"@inglorious/utils": "5.1.0",
|
|
311
|
+
"@inglorious/web": "6.8.1"
|
|
312
312
|
},
|
|
313
313
|
"devDependencies": {
|
|
314
314
|
"@iconify-icons/fa7-brands": "^2.0.0",
|
|
315
315
|
"@iconify-icons/fa7-regular": "^2.0.0",
|
|
316
316
|
"@iconify-icons/fa7-solid": "^2.0.0",
|
|
317
317
|
"@iconify-icons/material-symbols": "^2.0.6",
|
|
318
|
-
"@inglorious/charts": "11.1.
|
|
318
|
+
"@inglorious/charts": "11.1.6",
|
|
319
319
|
"@inglorious/eslint-config": "1.1.4",
|
|
320
|
-
"@inglorious/store": "
|
|
321
|
-
"@inglorious/vite-plugin-jsx": "2.0.
|
|
322
|
-
"@inglorious/vite-plugin-vue": "1.0.
|
|
323
|
-
"@inglorious/web": "6.
|
|
320
|
+
"@inglorious/store": "12.0.0",
|
|
321
|
+
"@inglorious/vite-plugin-jsx": "2.0.5",
|
|
322
|
+
"@inglorious/vite-plugin-vue": "1.0.6",
|
|
323
|
+
"@inglorious/web": "6.8.1",
|
|
324
324
|
"@storybook/addon-docs": "^10.6.0",
|
|
325
325
|
"@storybook/web-components-vite": "^10.6.0",
|
|
326
326
|
"happy-dom": "^20.14.0",
|
|
@@ -7,7 +7,7 @@ import { html } from "@inglorious/web"
|
|
|
7
7
|
import { classMap } from "@inglorious/web/directives/class-map"
|
|
8
8
|
import { ref } from "@inglorious/web/directives/ref"
|
|
9
9
|
|
|
10
|
-
import { applyElementProps } from "../../shared/
|
|
10
|
+
import { applyElementProps } from "../../shared/apply-element-props.js"
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Renders a themed button for actions and form submission.
|
|
@@ -7,7 +7,7 @@ import { html } from "@inglorious/web"
|
|
|
7
7
|
import { classMap } from "@inglorious/web/directives/class-map"
|
|
8
8
|
import { ref } from "@inglorious/web/directives/ref"
|
|
9
9
|
|
|
10
|
-
import { applyElementProps } from "../../shared/
|
|
10
|
+
import { applyElementProps } from "../../shared/apply-element-props.js"
|
|
11
11
|
import { Button } from "../button/index.js"
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* @typedef {import('@inglorious/web').TemplateResult} TemplateResult
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { html } from "@inglorious/web"
|
|
6
|
+
import { html, nothing } from "@inglorious/web"
|
|
7
7
|
import { classMap } from "@inglorious/web/directives/class-map"
|
|
8
8
|
import { ref } from "@inglorious/web/directives/ref"
|
|
9
9
|
|
|
10
|
-
import { applyElementProps } from "../../shared/
|
|
10
|
+
import { applyElementProps } from "../../shared/apply-element-props.js"
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Renders a labeled checkbox for boolean input.
|
|
@@ -30,6 +30,8 @@ export function render(props) {
|
|
|
30
30
|
...rest
|
|
31
31
|
} = props
|
|
32
32
|
|
|
33
|
+
// The label wraps the input, which is what ties the two together: it needs no
|
|
34
|
+
// `for`, and an empty one would point at nothing and break the click-through.
|
|
33
35
|
const inputId = id || name
|
|
34
36
|
|
|
35
37
|
const classes = {
|
|
@@ -40,10 +42,10 @@ export function render(props) {
|
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
return html`
|
|
43
|
-
<label class=${classMap(classes)}
|
|
45
|
+
<label class=${classMap(classes)}>
|
|
44
46
|
<input
|
|
45
|
-
id=${inputId}
|
|
46
|
-
class="iw-checkbox"
|
|
47
|
+
id=${inputId || nothing}
|
|
48
|
+
class="iw-checkbox-input"
|
|
47
49
|
type="checkbox"
|
|
48
50
|
name=${name}
|
|
49
51
|
.checked=${isChecked}
|
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
* @typedef {import('@inglorious/web').TemplateResult} TemplateResult
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { html } from "@inglorious/web"
|
|
7
|
+
import { html, nothing } from "@inglorious/web"
|
|
8
8
|
import { classMap } from "@inglorious/web/directives/class-map"
|
|
9
9
|
import { ref } from "@inglorious/web/directives/ref"
|
|
10
10
|
import { repeat } from "@inglorious/web/directives/repeat"
|
|
11
11
|
import { when } from "@inglorious/web/directives/when"
|
|
12
12
|
|
|
13
13
|
import { Chip } from "../../data-display/chip/index.js"
|
|
14
|
+
import { fallbackLabel } from "../../shared/fallback-label.js"
|
|
14
15
|
import {
|
|
15
16
|
filterOptions,
|
|
16
17
|
formatOption,
|
|
@@ -66,7 +67,7 @@ export const Combobox = {
|
|
|
66
67
|
${when(
|
|
67
68
|
label,
|
|
68
69
|
() =>
|
|
69
|
-
html`<label id=${labelId ||
|
|
70
|
+
html`<label id=${labelId || nothing} class="iw-combobox-label">
|
|
70
71
|
${label}
|
|
71
72
|
${when(
|
|
72
73
|
isRequired,
|
|
@@ -79,14 +80,16 @@ export const Combobox = {
|
|
|
79
80
|
${when(
|
|
80
81
|
error,
|
|
81
82
|
() =>
|
|
82
|
-
html`<span
|
|
83
|
+
html`<span
|
|
84
|
+
id=${messageId || nothing}
|
|
85
|
+
class="iw-combobox-error-message"
|
|
83
86
|
>${error}</span
|
|
84
87
|
>`,
|
|
85
88
|
)}
|
|
86
89
|
${when(
|
|
87
90
|
!error && hint,
|
|
88
91
|
() =>
|
|
89
|
-
html`<span id=${messageId ||
|
|
92
|
+
html`<span id=${messageId || nothing} class="iw-combobox-hint"
|
|
90
93
|
>${hint}</span
|
|
91
94
|
>`,
|
|
92
95
|
)}
|
|
@@ -107,6 +110,7 @@ export const Combobox = {
|
|
|
107
110
|
renderControl(props) {
|
|
108
111
|
const {
|
|
109
112
|
id,
|
|
113
|
+
label,
|
|
110
114
|
labelId,
|
|
111
115
|
messageId,
|
|
112
116
|
selectedValue,
|
|
@@ -134,14 +138,15 @@ export const Combobox = {
|
|
|
134
138
|
}
|
|
135
139
|
|
|
136
140
|
return html`<div
|
|
137
|
-
id=${controlId}
|
|
141
|
+
id=${controlId || nothing}
|
|
138
142
|
class="${classMap(frameClasses)}"
|
|
139
143
|
role="combobox"
|
|
140
144
|
aria-haspopup="listbox"
|
|
141
145
|
aria-expanded=${isOpen ? "true" : "false"}
|
|
142
|
-
aria-controls=${controlId ? `${controlId}-listbox` :
|
|
143
|
-
aria-labelledby=${labelId ||
|
|
144
|
-
aria-
|
|
146
|
+
aria-controls=${controlId ? `${controlId}-listbox` : nothing}
|
|
147
|
+
aria-labelledby=${labelId || nothing}
|
|
148
|
+
aria-label=${fallbackLabel(labelId, label)}
|
|
149
|
+
aria-describedby=${messageId || nothing}
|
|
145
150
|
aria-required=${isRequired ? "true" : "false"}
|
|
146
151
|
aria-invalid=${error ? "true" : "false"}
|
|
147
152
|
tabindex=${isDisabled ? -1 : 0}
|
|
@@ -235,7 +240,7 @@ export const Combobox = {
|
|
|
235
240
|
const { id, options, size, onOptionSelect, onChange } = props
|
|
236
241
|
|
|
237
242
|
const option = options.find((opt) => getOptionValue(opt) === value)
|
|
238
|
-
const tagId = `${id}-tag-${String(value)}`
|
|
243
|
+
const tagId = id ? `${id}-tag-${String(value)}` : undefined
|
|
239
244
|
const chipSize = size === "lg" ? "md" : "sm"
|
|
240
245
|
|
|
241
246
|
return html`<span
|
|
@@ -268,7 +273,7 @@ export const Combobox = {
|
|
|
268
273
|
const filteredOptions = filterOptions(options, searchTerm)
|
|
269
274
|
|
|
270
275
|
return html`<div
|
|
271
|
-
id=${id ? `${id}-control-listbox` :
|
|
276
|
+
id=${id ? `${id}-control-listbox` : nothing}
|
|
272
277
|
class="iw-combobox-dropdown"
|
|
273
278
|
role="listbox"
|
|
274
279
|
${ref((el) => trackOutsideClick(props, el))}
|
|
@@ -354,7 +359,7 @@ export const Combobox = {
|
|
|
354
359
|
const isFocused = index === focusedIndex
|
|
355
360
|
|
|
356
361
|
return html`<div
|
|
357
|
-
id=${id ? `${id}-option-${index}` :
|
|
362
|
+
id=${id ? `${id}-option-${index}` : nothing}
|
|
358
363
|
role="option"
|
|
359
364
|
aria-selected=${isSelected ? "true" : "false"}
|
|
360
365
|
class="iw-combobox-dropdown-options-option ${classMap({
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
* @typedef {import('@inglorious/web').TemplateResult} TemplateResult
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { html } from "@inglorious/web"
|
|
6
|
+
import { html, nothing } from "@inglorious/web"
|
|
7
7
|
import { classMap } from "@inglorious/web/directives/class-map"
|
|
8
8
|
import { ref } from "@inglorious/web/directives/ref"
|
|
9
9
|
|
|
10
|
-
import { applyElementProps } from "../../shared/
|
|
10
|
+
import { applyElementProps } from "../../shared/apply-element-props.js"
|
|
11
|
+
import { fallbackLabel } from "../../shared/fallback-label.js"
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Renders a labeled file upload area with optional summary, hint, and error text.
|
|
@@ -36,7 +37,8 @@ export function render(props) {
|
|
|
36
37
|
...rest
|
|
37
38
|
} = props
|
|
38
39
|
|
|
39
|
-
const uploadId = id || name
|
|
40
|
+
const uploadId = id || name
|
|
41
|
+
const messageId = (error || hint) && id ? `${id}-message` : ""
|
|
40
42
|
|
|
41
43
|
const wrapperClasses = {
|
|
42
44
|
"iw-upload": true,
|
|
@@ -58,7 +60,7 @@ export function render(props) {
|
|
|
58
60
|
${
|
|
59
61
|
label
|
|
60
62
|
? html`
|
|
61
|
-
<label for=${uploadId} class="iw-upload-label">
|
|
63
|
+
<label for=${uploadId || nothing} class="iw-upload-label">
|
|
62
64
|
${label}
|
|
63
65
|
${
|
|
64
66
|
isRequired
|
|
@@ -72,8 +74,11 @@ export function render(props) {
|
|
|
72
74
|
|
|
73
75
|
<div class=${classMap(fieldClasses)}>
|
|
74
76
|
<input
|
|
75
|
-
id=${uploadId}
|
|
77
|
+
id=${uploadId || nothing}
|
|
76
78
|
name=${name}
|
|
79
|
+
aria-label=${fallbackLabel(uploadId, label)}
|
|
80
|
+
aria-describedby=${messageId || nothing}
|
|
81
|
+
aria-invalid=${error ? "true" : "false"}
|
|
77
82
|
type="file"
|
|
78
83
|
accept=${accept ?? ""}
|
|
79
84
|
?multiple=${isMultiple}
|
|
@@ -96,9 +101,15 @@ export function render(props) {
|
|
|
96
101
|
|
|
97
102
|
${
|
|
98
103
|
error
|
|
99
|
-
? html`<span
|
|
104
|
+
? html`<span
|
|
105
|
+
id=${messageId || nothing}
|
|
106
|
+
class="iw-upload-error-message"
|
|
107
|
+
>${error}</span
|
|
108
|
+
>`
|
|
100
109
|
: hint
|
|
101
|
-
? html`<span class="iw-upload-hint"
|
|
110
|
+
? html`<span id=${messageId || nothing} class="iw-upload-hint"
|
|
111
|
+
>${hint}</span
|
|
112
|
+
>`
|
|
102
113
|
: null
|
|
103
114
|
}
|
|
104
115
|
</div>
|
|
@@ -3,20 +3,21 @@
|
|
|
3
3
|
* @typedef {import('@inglorious/web').TemplateResult} TemplateResult
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { html } from "@inglorious/web"
|
|
6
|
+
import { html, nothing } from "@inglorious/web"
|
|
7
7
|
import { classMap } from "@inglorious/web/directives/class-map"
|
|
8
8
|
import { ref } from "@inglorious/web/directives/ref"
|
|
9
9
|
import { when } from "@inglorious/web/directives/when"
|
|
10
10
|
|
|
11
11
|
import { Icon } from "../../data-display/icon/index.js"
|
|
12
|
-
import { applyElementProps } from "../../shared/
|
|
12
|
+
import { applyElementProps } from "../../shared/apply-element-props.js"
|
|
13
|
+
import { fallbackLabel } from "../../shared/fallback-label.js"
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Renders a labeled input field with optional icons, hint text, and error messaging.
|
|
16
17
|
* Supports disabled/readonly/required states and emits `onChange` on input.
|
|
17
18
|
* @example
|
|
18
19
|
* // Stateless usage
|
|
19
|
-
* input.render({ label: 'Email',
|
|
20
|
+
* input.render({ label: 'Email', inputType: 'email', placeholder: 'you@example.com' })
|
|
20
21
|
*
|
|
21
22
|
* @param {InputProps} props
|
|
22
23
|
* @returns {TemplateResult}
|
|
@@ -58,7 +59,7 @@ export const Input = {
|
|
|
58
59
|
${when(
|
|
59
60
|
label,
|
|
60
61
|
() => html`
|
|
61
|
-
<label for=${controlId} class="iw-input-label">
|
|
62
|
+
<label for=${controlId || nothing} class="iw-input-label">
|
|
62
63
|
${label}
|
|
63
64
|
${when(
|
|
64
65
|
isRequired,
|
|
@@ -67,18 +68,18 @@ export const Input = {
|
|
|
67
68
|
</label>
|
|
68
69
|
`,
|
|
69
70
|
)}
|
|
70
|
-
${this.renderControl?.(props)}
|
|
71
|
+
${this.renderControl?.({ ...props, messageId })}
|
|
71
72
|
${when(
|
|
72
73
|
error,
|
|
73
74
|
() =>
|
|
74
|
-
html`<span id=${messageId ||
|
|
75
|
+
html`<span id=${messageId || nothing} class="iw-input-error-message"
|
|
75
76
|
>${error}</span
|
|
76
77
|
>`,
|
|
77
78
|
)}
|
|
78
79
|
${when(
|
|
79
80
|
!error && hint,
|
|
80
81
|
() =>
|
|
81
|
-
html`<span id=${messageId ||
|
|
82
|
+
html`<span id=${messageId || nothing} class="iw-input-hint"
|
|
82
83
|
>${hint}</span
|
|
83
84
|
>`,
|
|
84
85
|
)}
|
|
@@ -91,6 +92,9 @@ export const Input = {
|
|
|
91
92
|
id,
|
|
92
93
|
type, // eslint-disable-line no-unused-vars
|
|
93
94
|
name = "",
|
|
95
|
+
label,
|
|
96
|
+
hint, // eslint-disable-line no-unused-vars
|
|
97
|
+
messageId,
|
|
94
98
|
inputType = "text",
|
|
95
99
|
value = "",
|
|
96
100
|
placeholder = "",
|
|
@@ -99,6 +103,7 @@ export const Input = {
|
|
|
99
103
|
isDisabled = false,
|
|
100
104
|
isReadOnly = false,
|
|
101
105
|
isRequired = false,
|
|
106
|
+
isFullWidth, // eslint-disable-line no-unused-vars
|
|
102
107
|
icon,
|
|
103
108
|
iconAfter,
|
|
104
109
|
onChange,
|
|
@@ -121,9 +126,12 @@ export const Input = {
|
|
|
121
126
|
${when(icon, () => Icon.render({ children: icon, color: "var(--iw-color-text-muted)", size }))}
|
|
122
127
|
|
|
123
128
|
<input
|
|
124
|
-
id=${controlId}
|
|
129
|
+
id=${controlId || nothing}
|
|
125
130
|
name=${name}
|
|
126
131
|
type=${inputType}
|
|
132
|
+
aria-label=${fallbackLabel(controlId, label)}
|
|
133
|
+
aria-describedby=${messageId || nothing}
|
|
134
|
+
aria-invalid=${error ? "true" : "false"}
|
|
127
135
|
.value=${value}
|
|
128
136
|
placeholder=${placeholder}
|
|
129
137
|
?disabled=${isDisabled}
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* @typedef {import('@inglorious/web').TemplateResult} TemplateResult
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { html } from "@inglorious/web"
|
|
6
|
+
import { html, nothing } from "@inglorious/web"
|
|
7
7
|
import { classMap } from "@inglorious/web/directives/class-map"
|
|
8
8
|
import { ref } from "@inglorious/web/directives/ref"
|
|
9
9
|
|
|
10
|
-
import { applyElementProps } from "../../shared/
|
|
10
|
+
import { applyElementProps } from "../../shared/apply-element-props.js"
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Renders a group of radio options for single selection.
|
|
@@ -30,7 +30,9 @@ export function render(props) {
|
|
|
30
30
|
...rest
|
|
31
31
|
} = props
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
// Radios are a group by sharing a name: without one, checking an option does not
|
|
34
|
+
// uncheck the others and the arrow keys do not move between them.
|
|
35
|
+
const groupName = name || id
|
|
34
36
|
|
|
35
37
|
const rootClasses = {
|
|
36
38
|
"iw-radio-group": true,
|
|
@@ -50,7 +52,7 @@ export function render(props) {
|
|
|
50
52
|
? html`<legend class="iw-radio-group-label">${label}</legend>`
|
|
51
53
|
: null
|
|
52
54
|
}
|
|
53
|
-
${options.map((option) => {
|
|
55
|
+
${options.map((option, index) => {
|
|
54
56
|
const {
|
|
55
57
|
label: optionLabel,
|
|
56
58
|
value: optionValue,
|
|
@@ -61,10 +63,10 @@ export function render(props) {
|
|
|
61
63
|
return html`
|
|
62
64
|
<label class="iw-radio-option">
|
|
63
65
|
<input
|
|
64
|
-
id=${
|
|
66
|
+
id=${groupName ? `${groupName}-${index}` : nothing}
|
|
65
67
|
class="iw-radio"
|
|
66
68
|
type="radio"
|
|
67
|
-
name=${
|
|
69
|
+
name=${groupName || nothing}
|
|
68
70
|
value=${optionValue}
|
|
69
71
|
.checked=${value === optionValue}
|
|
70
72
|
?disabled=${isDisabled || optionDisabled}
|
|
@@ -3,50 +3,15 @@
|
|
|
3
3
|
* @typedef {import('@inglorious/web').TemplateResult} TemplateResult
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { html } from "@inglorious/web"
|
|
6
|
+
import { html, nothing } from "@inglorious/web"
|
|
7
7
|
import { classMap } from "@inglorious/web/directives/class-map"
|
|
8
|
-
import { ref } from "@inglorious/web/directives/ref"
|
|
9
8
|
import { repeat } from "@inglorious/web/directives/repeat"
|
|
10
9
|
import { when } from "@inglorious/web/directives/when"
|
|
11
10
|
|
|
12
11
|
import { Icon } from "../../data-display/icon/index.js"
|
|
13
|
-
import {
|
|
12
|
+
import { fallbackLabel } from "../../shared/fallback-label.js"
|
|
14
13
|
import { formatOption, getOptionValue, isOptionSelected } from "./helpers.js"
|
|
15
14
|
|
|
16
|
-
/**
|
|
17
|
-
* Opens the native dropdown when the arrow strip is pressed.
|
|
18
|
-
*
|
|
19
|
-
* The arrow is a sibling of the select, not an overlay on it, so a press on it
|
|
20
|
-
* has no control underneath to hit. A native popup only opens from a trusted
|
|
21
|
-
* gesture on the select itself, and the strip is outside it.
|
|
22
|
-
*
|
|
23
|
-
* `showPicker()` is the API for that. It needs transient user activation, which
|
|
24
|
-
* a real click has and a dispatched event does not, and it throws rather than
|
|
25
|
-
* silently doing nothing, so the fallback covers the browsers without it.
|
|
26
|
-
*
|
|
27
|
-
* Deliberately on `click`, not `pointerdown`: focusing from pointerdown gets
|
|
28
|
-
* the focus moved away again by the browser's own mousedown handling, which
|
|
29
|
-
* showed up as a focus immediately followed by a blur.
|
|
30
|
-
*
|
|
31
|
-
* @param {MouseEvent} event
|
|
32
|
-
*/
|
|
33
|
-
function openFromArrowStrip(event) {
|
|
34
|
-
const frame = event.currentTarget
|
|
35
|
-
const select = frame?.querySelector("select")
|
|
36
|
-
if (!select || select.disabled || event.target === select) return
|
|
37
|
-
|
|
38
|
-
event.preventDefault()
|
|
39
|
-
if (typeof select.showPicker === "function") {
|
|
40
|
-
try {
|
|
41
|
-
select.showPicker()
|
|
42
|
-
return
|
|
43
|
-
} catch {
|
|
44
|
-
// No user activation, or the popup is already showing.
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
select.focus()
|
|
48
|
-
}
|
|
49
|
-
|
|
50
15
|
/**
|
|
51
16
|
* Renders a styled native select control with optional single or multi-select modes.
|
|
52
17
|
* Options can be primitives or objects and are rendered with consistent theming.
|
|
@@ -73,7 +38,6 @@ export const Select = {
|
|
|
73
38
|
isFullWidth = false,
|
|
74
39
|
} = props
|
|
75
40
|
|
|
76
|
-
const labelId = label && id ? `${id}-label` : ""
|
|
77
41
|
const messageId = (error || hint) && id ? `${id}-message` : ""
|
|
78
42
|
|
|
79
43
|
const selectClasses = {
|
|
@@ -86,7 +50,7 @@ export const Select = {
|
|
|
86
50
|
${when(
|
|
87
51
|
label,
|
|
88
52
|
() => html`
|
|
89
|
-
<label for=${id || name ||
|
|
53
|
+
<label for=${id || name || nothing} class="iw-select-label">
|
|
90
54
|
${label}
|
|
91
55
|
${
|
|
92
56
|
isRequired
|
|
@@ -96,18 +60,20 @@ export const Select = {
|
|
|
96
60
|
</label>
|
|
97
61
|
`,
|
|
98
62
|
)}
|
|
99
|
-
${this.renderControl?.({ ...props,
|
|
63
|
+
${this.renderControl?.({ ...props, messageId })}
|
|
100
64
|
${when(
|
|
101
65
|
error,
|
|
102
66
|
() =>
|
|
103
|
-
html`<span
|
|
67
|
+
html`<span
|
|
68
|
+
id=${messageId || nothing}
|
|
69
|
+
class="iw-select-error-message"
|
|
104
70
|
>${error}</span
|
|
105
71
|
>`,
|
|
106
72
|
)}
|
|
107
73
|
${when(
|
|
108
74
|
!error && hint,
|
|
109
75
|
() =>
|
|
110
|
-
html`<span id=${messageId ||
|
|
76
|
+
html`<span id=${messageId || nothing} class="iw-select-hint"
|
|
111
77
|
>${hint}</span
|
|
112
78
|
>`,
|
|
113
79
|
)}
|
|
@@ -119,6 +85,8 @@ export const Select = {
|
|
|
119
85
|
const {
|
|
120
86
|
id,
|
|
121
87
|
name,
|
|
88
|
+
label,
|
|
89
|
+
messageId,
|
|
122
90
|
options,
|
|
123
91
|
value,
|
|
124
92
|
error,
|
|
@@ -152,8 +120,11 @@ export const Select = {
|
|
|
152
120
|
${icon ? Icon.render({ children: icon, size }) : null}
|
|
153
121
|
|
|
154
122
|
<select
|
|
155
|
-
id=${controlId}
|
|
123
|
+
id=${controlId || nothing}
|
|
156
124
|
name=${name}
|
|
125
|
+
aria-label=${ariaLabel || fallbackLabel(controlId, label)}
|
|
126
|
+
aria-describedby=${messageId || nothing}
|
|
127
|
+
aria-invalid=${error ? "true" : "false"}
|
|
157
128
|
?multiple=${isMulti}
|
|
158
129
|
?disabled=${isDisabled}
|
|
159
130
|
?required=${isRequired}
|
|
@@ -161,9 +132,6 @@ export const Select = {
|
|
|
161
132
|
@change=${(event) => onChange?.(event.target.value)}
|
|
162
133
|
@blur=${onBlur}
|
|
163
134
|
@focus=${onFocus}
|
|
164
|
-
${ref((element) =>
|
|
165
|
-
applyElementProps(element, { "aria-label": ariaLabel || null }),
|
|
166
|
-
)}
|
|
167
135
|
>
|
|
168
136
|
${repeat(
|
|
169
137
|
options,
|
|
@@ -206,3 +174,37 @@ export const Select = {
|
|
|
206
174
|
</div>`
|
|
207
175
|
},
|
|
208
176
|
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Opens the native dropdown when the arrow strip is pressed.
|
|
180
|
+
*
|
|
181
|
+
* The arrow is a sibling of the select, not an overlay on it, so a press on it
|
|
182
|
+
* has no control underneath to hit. A native popup only opens from a trusted
|
|
183
|
+
* gesture on the select itself, and the strip is outside it.
|
|
184
|
+
*
|
|
185
|
+
* `showPicker()` is the API for that. It needs transient user activation, which
|
|
186
|
+
* a real click has and a dispatched event does not, and it throws rather than
|
|
187
|
+
* silently doing nothing, so the fallback covers the browsers without it.
|
|
188
|
+
*
|
|
189
|
+
* Deliberately on `click`, not `pointerdown`: focusing from pointerdown gets
|
|
190
|
+
* the focus moved away again by the browser's own mousedown handling, which
|
|
191
|
+
* showed up as a focus immediately followed by a blur.
|
|
192
|
+
*
|
|
193
|
+
* @param {MouseEvent} event
|
|
194
|
+
*/
|
|
195
|
+
function openFromArrowStrip(event) {
|
|
196
|
+
const frame = event.currentTarget
|
|
197
|
+
const select = frame?.querySelector("select")
|
|
198
|
+
if (!select || select.disabled || event.target === select) return
|
|
199
|
+
|
|
200
|
+
event.preventDefault()
|
|
201
|
+
if (typeof select.showPicker === "function") {
|
|
202
|
+
try {
|
|
203
|
+
select.showPicker()
|
|
204
|
+
return
|
|
205
|
+
} catch {
|
|
206
|
+
// No user activation, or the popup is already showing.
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
select.focus()
|
|
210
|
+
}
|