@inglorious/ui 7.2.1 → 7.2.3
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 +4 -4
- package/src/controls/combobox/combobox.stories.js +66 -4
- package/src/controls/combobox/style.css +74 -23
- package/src/controls/combobox/template.js +242 -154
- package/src/controls/input/index.js +1 -1
- package/src/controls/input/input.stories.js +35 -0
- package/src/controls/input/style.css +43 -56
- package/src/controls/input/template.js +112 -88
- package/src/controls/select/index.js +1 -1
- package/src/controls/select/select.stories.js +35 -3
- package/src/controls/select/style.css +145 -66
- package/src/controls/select/template.js +178 -74
- package/src/data-display/icon/icon-implementations.mdx +2 -2
- package/types/controls/combobox.d.ts +3 -0
- package/types/controls/select.d.ts +4 -0
|
@@ -5,12 +5,46 @@
|
|
|
5
5
|
|
|
6
6
|
import { html } 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"
|
|
9
|
+
import { when } from "@inglorious/web/directives/when"
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { Icon } from "../../data-display/icon/index.js"
|
|
12
12
|
import { formatOption, getOptionValue, isOptionSelected } from "./helpers.js"
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Opens the native dropdown when the arrow strip is pressed.
|
|
16
|
+
*
|
|
17
|
+
* The arrow is a sibling of the select, not an overlay on it, so a press on it
|
|
18
|
+
* has no control underneath to hit. A native popup only opens from a trusted
|
|
19
|
+
* gesture on the select itself, and the strip is outside it.
|
|
20
|
+
*
|
|
21
|
+
* `showPicker()` is the API for that. It needs transient user activation, which
|
|
22
|
+
* a real click has and a dispatched event does not, and it throws rather than
|
|
23
|
+
* silently doing nothing, so the fallback covers the browsers without it.
|
|
24
|
+
*
|
|
25
|
+
* Deliberately on `click`, not `pointerdown`: focusing from pointerdown gets
|
|
26
|
+
* the focus moved away again by the browser's own mousedown handling, which
|
|
27
|
+
* showed up as a focus immediately followed by a blur.
|
|
28
|
+
*
|
|
29
|
+
* @param {MouseEvent} event
|
|
30
|
+
*/
|
|
31
|
+
function openFromArrowStrip(event) {
|
|
32
|
+
const frame = event.currentTarget
|
|
33
|
+
const select = frame?.querySelector("select")
|
|
34
|
+
if (!select || select.disabled || event.target === select) return
|
|
35
|
+
|
|
36
|
+
event.preventDefault()
|
|
37
|
+
if (typeof select.showPicker === "function") {
|
|
38
|
+
try {
|
|
39
|
+
select.showPicker()
|
|
40
|
+
return
|
|
41
|
+
} catch {
|
|
42
|
+
// No user activation, or the popup is already showing.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
select.focus()
|
|
46
|
+
}
|
|
47
|
+
|
|
14
48
|
/**
|
|
15
49
|
* Renders a styled native select control with optional single or multi-select modes.
|
|
16
50
|
* Options can be primitives or objects and are rendered with consistent theming.
|
|
@@ -21,77 +55,147 @@ import { formatOption, getOptionValue, isOptionSelected } from "./helpers.js"
|
|
|
21
55
|
* @param {SelectProps} props
|
|
22
56
|
* @returns {TemplateResult}
|
|
23
57
|
*/
|
|
24
|
-
export
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
export const Select = {
|
|
59
|
+
render(props) {
|
|
60
|
+
return this.renderSelect(props)
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
renderSelect(props) {
|
|
64
|
+
const {
|
|
65
|
+
id,
|
|
66
|
+
label,
|
|
67
|
+
hint,
|
|
68
|
+
error,
|
|
69
|
+
isRequired = false,
|
|
70
|
+
isFullWidth = false,
|
|
71
|
+
} = props
|
|
72
|
+
|
|
73
|
+
const labelId = label && id ? `${id}-label` : ""
|
|
74
|
+
const messageId = (error || hint) && id ? `${id}-message` : ""
|
|
75
|
+
|
|
76
|
+
const selectClasses = {
|
|
77
|
+
"iw-select": true,
|
|
78
|
+
"iw-select-full-width": !!isFullWidth,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return html`
|
|
82
|
+
<div class=${classMap(selectClasses)}>
|
|
83
|
+
${when(
|
|
84
|
+
label,
|
|
85
|
+
() => html`
|
|
86
|
+
<label for=${id || name || ""} class="iw-select-label">
|
|
87
|
+
${label}
|
|
88
|
+
${
|
|
89
|
+
isRequired
|
|
90
|
+
? html`<span class="iw-select-required">*</span>`
|
|
91
|
+
: null
|
|
92
|
+
}
|
|
93
|
+
</label>
|
|
94
|
+
`,
|
|
95
|
+
)}
|
|
96
|
+
${this.renderControl?.({ ...props, labelId, messageId })}
|
|
97
|
+
${when(
|
|
98
|
+
error,
|
|
99
|
+
() =>
|
|
100
|
+
html`<span id=${messageId || ""} class="iw-select-error-message"
|
|
101
|
+
>${error}</span
|
|
102
|
+
>`,
|
|
103
|
+
)}
|
|
104
|
+
${when(
|
|
105
|
+
!error && hint,
|
|
106
|
+
() =>
|
|
107
|
+
html`<span id=${messageId || ""} class="iw-select-hint"
|
|
108
|
+
>${hint}</span
|
|
109
|
+
>`,
|
|
110
|
+
)}
|
|
111
|
+
</div>
|
|
112
|
+
`
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
renderControl(props) {
|
|
116
|
+
const {
|
|
117
|
+
id,
|
|
118
|
+
name,
|
|
119
|
+
options,
|
|
120
|
+
value,
|
|
121
|
+
error,
|
|
122
|
+
icon,
|
|
123
|
+
size = "md",
|
|
124
|
+
isOpen,
|
|
125
|
+
isMulti,
|
|
126
|
+
isRequired,
|
|
127
|
+
isDisabled,
|
|
128
|
+
onChange,
|
|
129
|
+
onBlur,
|
|
130
|
+
onFocus,
|
|
131
|
+
} = props
|
|
132
|
+
|
|
133
|
+
const controlId = id || name
|
|
134
|
+
|
|
135
|
+
const controlClasses = {
|
|
136
|
+
"iw-select-control": true,
|
|
137
|
+
[`iw-select-${size}`]: true,
|
|
138
|
+
"iw-select-control-open": isOpen,
|
|
139
|
+
"iw-select-control-disabled": isDisabled,
|
|
140
|
+
"iw-select-control-error": !!error,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const selectedOption = options.find(
|
|
144
|
+
(option) => getOptionValue(option) === value,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return html`<div class="iw-select-frame" @click=${openFromArrowStrip}>
|
|
148
|
+
${icon ? Icon.render({ children: icon, size }) : null}
|
|
149
|
+
|
|
150
|
+
<select
|
|
151
|
+
id=${controlId}
|
|
152
|
+
name=${name}
|
|
153
|
+
?multiple=${isMulti}
|
|
154
|
+
?disabled=${isDisabled}
|
|
155
|
+
?required=${isRequired}
|
|
156
|
+
class=${classMap(controlClasses)}
|
|
157
|
+
@change=${(event) => onChange?.(event.target.value)}
|
|
158
|
+
@blur=${onBlur}
|
|
159
|
+
@focus=${onFocus}
|
|
160
|
+
>
|
|
161
|
+
${repeat(
|
|
162
|
+
options,
|
|
163
|
+
(option) => getOptionValue(option),
|
|
164
|
+
(option) => {
|
|
165
|
+
const {
|
|
166
|
+
value,
|
|
167
|
+
label,
|
|
168
|
+
isDisabled: isOptionDisabled,
|
|
169
|
+
} = formatOption(option)
|
|
170
|
+
const isSelected = isOptionSelected(
|
|
171
|
+
option,
|
|
172
|
+
selectedOption?.value,
|
|
173
|
+
isMulti,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
const optionClasses = {
|
|
177
|
+
"iw-select-option": true,
|
|
178
|
+
"iw-select-option-selected": isSelected,
|
|
179
|
+
}
|
|
48
180
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const {
|
|
70
|
-
value,
|
|
71
|
-
label,
|
|
72
|
-
isDisabled: isOptionDisabled,
|
|
73
|
-
} = formatOption(option)
|
|
74
|
-
const isSelected = isOptionSelected(
|
|
75
|
-
option,
|
|
76
|
-
selectedOption?.value,
|
|
77
|
-
isMulti,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
const optionClasses = {
|
|
81
|
-
"iw-select-option": true,
|
|
82
|
-
"iw-select-option-selected": isSelected,
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return html`<option
|
|
86
|
-
value=${value}
|
|
87
|
-
.selected=${value === selectedOption?.value}
|
|
88
|
-
?disabled=${isOptionDisabled}
|
|
89
|
-
class=${classMap(optionClasses)}
|
|
90
|
-
>
|
|
91
|
-
${label}
|
|
92
|
-
</option>`
|
|
93
|
-
},
|
|
94
|
-
)}
|
|
95
|
-
</select>
|
|
96
|
-
`
|
|
181
|
+
return html`<option
|
|
182
|
+
value=${value}
|
|
183
|
+
.selected=${value === selectedOption?.value}
|
|
184
|
+
?disabled=${isOptionDisabled}
|
|
185
|
+
class=${classMap(optionClasses)}
|
|
186
|
+
>
|
|
187
|
+
${label}
|
|
188
|
+
</option>`
|
|
189
|
+
},
|
|
190
|
+
)}
|
|
191
|
+
</select>
|
|
192
|
+
<!-- A native select can only contain options, so the arrow cannot live
|
|
193
|
+
inside it. It sits on a wrapper around the select rather than the
|
|
194
|
+
field, so it stays centred on the control whether or not a label,
|
|
195
|
+
hint or error sits above and below it. Decorative: pointer-events
|
|
196
|
+
none so clicks reach the select, and hidden from assistive tech,
|
|
197
|
+
which announces a dropdown without needing the glyph. -->
|
|
198
|
+
<span class="iw-select-arrow" aria-hidden="true">▼</span>
|
|
199
|
+
</div>`
|
|
200
|
+
},
|
|
97
201
|
}
|
|
@@ -108,8 +108,8 @@ Use this page as a quick reference for supported icon strategies in `@inglorious
|
|
|
108
108
|
</section>
|
|
109
109
|
|
|
110
110
|
<section className="iw-icon-tab-panel iw-icon-tab-panel-material">
|
|
111
|
-
<p>Use <code>@inglorious/ui/material-icon</code> when Material Symbols fonts are loaded. Reference: <a href="https://fonts.google.com/icons?icon.set=Material+
|
|
112
|
-
<pre><code>{`MaterialIcon.render({ name: "home",
|
|
111
|
+
<p>Use <code>@inglorious/ui/material-icon</code> when Material Symbols fonts are loaded. Reference: <a href="https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Outlined" target="_blank">Material Symbols</a></p>
|
|
112
|
+
<pre><code>{`MaterialIcon.render({ name: "home", isFilled: true, size: "md" })`}</code></pre>
|
|
113
113
|
<div className="iw-icon-example">
|
|
114
114
|
<span className="iw-material-symbol" aria-hidden="true">home</span>
|
|
115
115
|
<span className="iw-material-symbol" aria-hidden="true">settings</span>
|
|
@@ -19,7 +19,11 @@ export interface SelectProps {
|
|
|
19
19
|
size?: "sm" | "md" | "lg"
|
|
20
20
|
isDisabled?: boolean
|
|
21
21
|
isFullWidth?: boolean
|
|
22
|
+
label?: string
|
|
23
|
+
hint?: string
|
|
24
|
+
error?: string
|
|
22
25
|
isMulti?: boolean
|
|
26
|
+
isRequired?: boolean
|
|
23
27
|
onChange?: (value: string) => void
|
|
24
28
|
onBlur?: () => void
|
|
25
29
|
onFocus?: () => void
|