@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.
Files changed (88) hide show
  1. package/package.json +9 -9
  2. package/src/controls/button/button.stories.js +1 -1
  3. package/src/controls/button/template.js +1 -1
  4. package/src/controls/button-group/button-group.stories.js +1 -1
  5. package/src/controls/button-group/template.js +1 -1
  6. package/src/controls/checkbox/checkbox.stories.js +1 -1
  7. package/src/controls/checkbox/style.css +1 -1
  8. package/src/controls/checkbox/template.js +7 -5
  9. package/src/controls/combobox/combobox.stories.js +1 -1
  10. package/src/controls/combobox/template.js +16 -11
  11. package/src/controls/fab/fab.stories.js +1 -1
  12. package/src/controls/file-upload/file-upload.stories.js +1 -1
  13. package/src/controls/file-upload/template.js +18 -7
  14. package/src/controls/icon-button/icon-button.stories.js +1 -1
  15. package/src/controls/image-cropper/image-cropper.stories.js +1 -1
  16. package/src/controls/input/input.stories.js +1 -1
  17. package/src/controls/input/template.js +16 -8
  18. package/src/controls/radio-group/radio-group.stories.js +1 -1
  19. package/src/controls/radio-group/template.js +8 -6
  20. package/src/controls/rating/rating.stories.js +1 -1
  21. package/src/controls/select/select.stories.js +1 -1
  22. package/src/controls/select/template.js +48 -46
  23. package/src/controls/slider/slider.stories.js +1 -1
  24. package/src/controls/slider/template.js +6 -4
  25. package/src/controls/switch/switch.stories.js +1 -1
  26. package/src/controls/switch/template.js +6 -4
  27. package/src/controls/textarea/template.js +17 -6
  28. package/src/controls/textarea/textarea.stories.js +1 -1
  29. package/src/data-display/avatar/avatar.stories.js +1 -1
  30. package/src/data-display/badge/badge.stories.js +1 -1
  31. package/src/data-display/before-after/before-after.stories.js +1 -1
  32. package/src/data-display/carousel/carousel.stories.js +10 -9
  33. package/src/data-display/chip/chip.stories.js +1 -1
  34. package/src/data-display/data-grid/data-grid.stories.js +11 -11
  35. package/src/data-display/divider/divider.stories.js +1 -1
  36. package/src/data-display/icon/icon-utf8.stories.js +1 -1
  37. package/src/data-display/icon/icon.stories.js +1 -1
  38. package/src/data-display/icon/template.js +1 -1
  39. package/src/data-display/list/list.stories.js +1 -1
  40. package/src/data-display/list/template.js +1 -1
  41. package/src/data-display/table/table.stories.js +1 -1
  42. package/src/data-display/table/template.js +1 -1
  43. package/src/data-display/tooltip/template.js +1 -1
  44. package/src/data-display/tooltip/tooltip.stories.js +1 -1
  45. package/src/data-display/typography/template.js +1 -1
  46. package/src/data-display/typography/typography.stories.js +1 -1
  47. package/src/data-display/virtual-list/template.js +21 -21
  48. package/src/examples/dashboard/before-after-data.js +17 -14
  49. package/src/examples/dashboard/dashboard.stories.js +1 -1
  50. package/src/examples/dashboard/primitive-content.js +32 -30
  51. package/src/examples/dashboard/theme-utils.js +5 -5
  52. package/src/feedback/alert/alert.stories.js +1 -1
  53. package/src/feedback/alert/template.js +1 -1
  54. package/src/feedback/backdrop/backdrop.stories.js +1 -1
  55. package/src/feedback/dialog/dialog.stories.js +1 -1
  56. package/src/feedback/progress/progress.stories.js +1 -1
  57. package/src/feedback/progress/template.js +4 -4
  58. package/src/feedback/skeleton/skeleton.stories.js +1 -1
  59. package/src/feedback/snackbar/snackbar.stories.js +1 -1
  60. package/src/layout/container/container.stories.js +1 -1
  61. package/src/layout/container/template.js +1 -1
  62. package/src/layout/flex/flex.stories.js +1 -1
  63. package/src/layout/flex/template.js +1 -1
  64. package/src/layout/grid/grid.stories.js +1 -1
  65. package/src/navigation/bottom-navigation/bottom-navigation.stories.js +1 -1
  66. package/src/navigation/bottom-navigation/template.js +1 -1
  67. package/src/navigation/breadcrumbs/breadcrumbs.stories.js +1 -1
  68. package/src/navigation/breadcrumbs/template.js +1 -1
  69. package/src/navigation/drawer/drawer.stories.js +1 -1
  70. package/src/navigation/link/link.stories.js +1 -1
  71. package/src/navigation/link/template.js +1 -1
  72. package/src/navigation/menu/menu.stories.js +1 -1
  73. package/src/navigation/menu/template.js +1 -1
  74. package/src/navigation/pagination/pagination.stories.js +1 -1
  75. package/src/navigation/speed-dial/speed-dial.stories.js +1 -1
  76. package/src/navigation/stepper/stepper.stories.js +1 -1
  77. package/src/navigation/tabs/tabs.stories.js +1 -1
  78. package/src/shared/fallback-label.js +18 -0
  79. package/src/surfaces/accordion/accordion.stories.js +1 -1
  80. package/src/surfaces/accordion/template.js +1 -1
  81. package/src/surfaces/app-bar/app-bar.stories.js +1 -1
  82. package/src/surfaces/app-bar/template.js +1 -1
  83. package/src/surfaces/card/card.stories.js +1 -1
  84. package/src/surfaces/card/template.js +1 -1
  85. package/src/surfaces/paper/paper.stories.js +1 -1
  86. package/src/surfaces/paper/template.js +1 -1
  87. package/src/shared/{applyElementProps.js → apply-element-props.js} +27 -27
  88. /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.1",
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": "11.3.0",
310
- "@inglorious/utils": "5.0.0",
311
- "@inglorious/web": "6.7.2"
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.4",
318
+ "@inglorious/charts": "11.1.6",
319
319
  "@inglorious/eslint-config": "1.1.4",
320
- "@inglorious/store": "11.3.0",
321
- "@inglorious/vite-plugin-jsx": "2.0.4",
322
- "@inglorious/vite-plugin-vue": "1.0.5",
323
- "@inglorious/web": "6.7.2",
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",
@@ -1,6 +1,6 @@
1
1
  import { html } from "@inglorious/web"
2
2
 
3
- import { createRender } from "../../stories/notifyStory.js"
3
+ import { createRender } from "../../stories/notify-story.js"
4
4
  import { Button } from "."
5
5
 
6
6
  export default {
@@ -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/applyElementProps.js"
10
+ import { applyElementProps } from "../../shared/apply-element-props.js"
11
11
 
12
12
  /**
13
13
  * Renders a themed button for actions and form submission.
@@ -1,4 +1,4 @@
1
- import { createRender } from "../../stories/notifyStory.js"
1
+ import { createRender } from "../../stories/notify-story.js"
2
2
  import { ButtonGroup } from "."
3
3
 
4
4
  export default {
@@ -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/applyElementProps.js"
10
+ import { applyElementProps } from "../../shared/apply-element-props.js"
11
11
  import { Button } from "../button/index.js"
12
12
 
13
13
  /**
@@ -1,4 +1,4 @@
1
- import { createRender } from "../../stories/notifyStory.js"
1
+ import { createRender } from "../../stories/notify-story.js"
2
2
  import { Checkbox } from "."
3
3
 
4
4
  export default {
@@ -7,7 +7,7 @@
7
7
  color: var(--iw-color-text);
8
8
  cursor: pointer;
9
9
 
10
- .iw-checkbox {
10
+ .iw-checkbox-input {
11
11
  width: 1rem;
12
12
  height: 1rem;
13
13
  margin: 0;
@@ -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/applyElementProps.js"
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)} for=${inputId}>
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}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createEntityRender,
3
3
  notifyActionArgType,
4
- } from "../../stories/notifyStory.js"
4
+ } from "../../stories/notify-story.js"
5
5
  import { Combobox } from "./index.js"
6
6
 
7
7
  const options = [
@@ -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 || ""} class="iw-combobox-label">
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 id=${messageId ?? ""} class="iw-combobox-error-message"
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 || ""} class="iw-combobox-hint"
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-describedby=${messageId || ""}
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({
@@ -1,7 +1,7 @@
1
1
  import { html } from "@inglorious/web"
2
2
 
3
3
  import { Icon } from "../../data-display/icon"
4
- import { createRender } from "../../stories/notifyStory.js"
4
+ import { createRender } from "../../stories/notify-story.js"
5
5
  import { Fab } from "."
6
6
 
7
7
  export default {
@@ -1,4 +1,4 @@
1
- import { createRender } from "../../stories/notifyStory.js"
1
+ import { createRender } from "../../stories/notify-story.js"
2
2
  import { FileUpload } from "./index.js"
3
3
 
4
4
  export default {
@@ -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/applyElementProps.js"
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 || "file-upload"
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 class="iw-upload-error-message">${error}</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">${hint}</span>`
110
+ ? html`<span id=${messageId || nothing} class="iw-upload-hint"
111
+ >${hint}</span
112
+ >`
102
113
  : null
103
114
  }
104
115
  </div>
@@ -1,4 +1,4 @@
1
- import { createRender } from "../../stories/notifyStory.js"
1
+ import { createRender } from "../../stories/notify-story.js"
2
2
  import { IconButton } from "."
3
3
 
4
4
  export default {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createEntityRender,
3
3
  notifyActionArgType,
4
- } from "../../stories/notifyStory.js"
4
+ } from "../../stories/notify-story.js"
5
5
  import { ImageCropper } from "./index.js"
6
6
 
7
7
  export default {
@@ -1,6 +1,6 @@
1
1
  import { html } from "@inglorious/web"
2
2
 
3
- import { createRender } from "../../stories/notifyStory.js"
3
+ import { createRender } from "../../stories/notify-story.js"
4
4
  import { Input } from "."
5
5
 
6
6
  const nativeInputTypes = [
@@ -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/applyElementProps.js"
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', type: 'email', placeholder: 'you@example.com' })
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 || ""} class="iw-input-error-message"
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 || ""} class="iw-input-hint"
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}
@@ -1,4 +1,4 @@
1
- import { createRender } from "../../stories/notifyStory.js"
1
+ import { createRender } from "../../stories/notify-story.js"
2
2
  import { RadioGroup } from "."
3
3
 
4
4
  export default {
@@ -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/applyElementProps.js"
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
- const inputId = id || name
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=${inputId}
66
+ id=${groupName ? `${groupName}-${index}` : nothing}
65
67
  class="iw-radio"
66
68
  type="radio"
67
- name=${name}
69
+ name=${groupName || nothing}
68
70
  value=${optionValue}
69
71
  .checked=${value === optionValue}
70
72
  ?disabled=${isDisabled || optionDisabled}
@@ -1,4 +1,4 @@
1
- import { createRender } from "../../stories/notifyStory.js"
1
+ import { createRender } from "../../stories/notify-story.js"
2
2
  import { Rating } from "."
3
3
 
4
4
  export default {
@@ -1,4 +1,4 @@
1
- import { createRender } from "../../stories/notifyStory.js"
1
+ import { createRender } from "../../stories/notify-story.js"
2
2
  import { Select } from "."
3
3
 
4
4
  export default {
@@ -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 { applyElementProps } from "../../shared/applyElementProps.js"
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 || ""} class="iw-select-label">
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, labelId, messageId })}
63
+ ${this.renderControl?.({ ...props, messageId })}
100
64
  ${when(
101
65
  error,
102
66
  () =>
103
- html`<span id=${messageId || ""} class="iw-select-error-message"
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 || ""} class="iw-select-hint"
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
+ }
@@ -1,4 +1,4 @@
1
- import { createRender } from "../../stories/notifyStory.js"
1
+ import { createRender } from "../../stories/notify-story.js"
2
2
  import { Slider } from "."
3
3
 
4
4
  export default {