@inglorious/ui 9.8.2 → 9.10.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/package.json +28 -3
- package/src/controls/chip-picker/chip-picker.stories.js +22 -0
- package/src/controls/chip-picker/index.js +3 -0
- package/src/controls/chip-picker/style.css +26 -0
- package/src/controls/chip-picker/template.js +53 -0
- package/src/controls/combobox/combobox.stories.js +14 -0
- package/src/controls/combobox/handlers.js +87 -20
- package/src/controls/combobox/helpers.js +50 -4
- package/src/controls/combobox/index.js +1 -0
- package/src/controls/combobox/template.js +13 -14
- package/src/controls/controls.css +3 -2
- package/src/data-display/data-display.css +1 -0
- package/src/data-display/lightbox/index.js +3 -0
- package/src/data-display/lightbox/lightbox.stories.js +25 -0
- package/src/data-display/lightbox/style.css +97 -0
- package/src/data-display/lightbox/template.js +153 -0
- package/src/feedback/confirm-dialog/confirm-dialog.stories.js +33 -0
- package/src/feedback/confirm-dialog/index.js +3 -0
- package/src/feedback/confirm-dialog/style.css +16 -0
- package/src/feedback/confirm-dialog/template.js +70 -0
- package/src/feedback/crop-dialog/index.js +3 -0
- package/src/feedback/crop-dialog/style.css +43 -0
- package/src/feedback/crop-dialog/template.js +82 -0
- package/src/feedback/dialog/style.css +2 -1
- package/src/feedback/dialog/template.js +5 -2
- package/src/feedback/feedback.css +3 -0
- package/src/feedback/load-status/index.js +3 -0
- package/src/feedback/load-status/load-status.stories.js +16 -0
- package/src/feedback/load-status/style.css +10 -0
- package/src/feedback/load-status/template.js +43 -0
- package/src/index.js +5 -0
- package/src/shared/icons.js +12 -0
- package/types/controls/chip-picker.d.ts +23 -0
- package/types/controls/combobox.d.ts +38 -2
- package/types/data-display/lightbox.d.ts +35 -0
- package/types/feedback/confirm-dialog.d.ts +34 -0
- package/types/feedback/crop-dialog.d.ts +35 -0
- package/types/feedback/load-status.d.ts +22 -0
- package/types/index.d.ts +17 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/ui",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.10.0",
|
|
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",
|
|
@@ -211,6 +211,31 @@
|
|
|
211
211
|
"import": "./src/feedback/backdrop/index.js"
|
|
212
212
|
},
|
|
213
213
|
"./backdrop.css": "./src/feedback/backdrop/style.css",
|
|
214
|
+
"./chip-picker": {
|
|
215
|
+
"types": "./types/controls/chip-picker.d.ts",
|
|
216
|
+
"import": "./src/controls/chip-picker/index.js"
|
|
217
|
+
},
|
|
218
|
+
"./chip-picker.css": "./src/controls/chip-picker/style.css",
|
|
219
|
+
"./confirm-dialog": {
|
|
220
|
+
"types": "./types/feedback/confirm-dialog.d.ts",
|
|
221
|
+
"import": "./src/feedback/confirm-dialog/index.js"
|
|
222
|
+
},
|
|
223
|
+
"./confirm-dialog.css": "./src/feedback/confirm-dialog/style.css",
|
|
224
|
+
"./crop-dialog": {
|
|
225
|
+
"types": "./types/feedback/crop-dialog.d.ts",
|
|
226
|
+
"import": "./src/feedback/crop-dialog/index.js"
|
|
227
|
+
},
|
|
228
|
+
"./crop-dialog.css": "./src/feedback/crop-dialog/style.css",
|
|
229
|
+
"./lightbox": {
|
|
230
|
+
"types": "./types/data-display/lightbox.d.ts",
|
|
231
|
+
"import": "./src/data-display/lightbox/index.js"
|
|
232
|
+
},
|
|
233
|
+
"./lightbox.css": "./src/data-display/lightbox/style.css",
|
|
234
|
+
"./load-status": {
|
|
235
|
+
"types": "./types/feedback/load-status.d.ts",
|
|
236
|
+
"import": "./src/feedback/load-status/index.js"
|
|
237
|
+
},
|
|
238
|
+
"./load-status.css": "./src/feedback/load-status/style.css",
|
|
214
239
|
"./dialog": {
|
|
215
240
|
"types": "./types/feedback/dialog.d.ts",
|
|
216
241
|
"import": "./src/feedback/dialog/index.js"
|
|
@@ -316,7 +341,7 @@
|
|
|
316
341
|
"peerDependencies": {
|
|
317
342
|
"@inglorious/store": "^12.1.1",
|
|
318
343
|
"@inglorious/utils": "^5.1.0",
|
|
319
|
-
"@inglorious/web": "^6.
|
|
344
|
+
"@inglorious/web": "^6.14.0"
|
|
320
345
|
},
|
|
321
346
|
"devDependencies": {
|
|
322
347
|
"@iconify-icons/fa7-brands": "^2.0.0",
|
|
@@ -328,7 +353,7 @@
|
|
|
328
353
|
"@inglorious/store": "12.1.1",
|
|
329
354
|
"@inglorious/vite-plugin-jsx": "2.0.6",
|
|
330
355
|
"@inglorious/vite-plugin-vue": "1.0.7",
|
|
331
|
-
"@inglorious/web": "6.
|
|
356
|
+
"@inglorious/web": "6.14.0",
|
|
332
357
|
"@storybook/addon-docs": "^10.6.5",
|
|
333
358
|
"@storybook/web-components-vite": "^10.6.5",
|
|
334
359
|
"happy-dom": "^20.14.0",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createRender } from "../../stories/notify-story.js"
|
|
2
|
+
import { ChipPicker } from "."
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: "Controls/ChipPicker",
|
|
6
|
+
tags: ["autodocs"],
|
|
7
|
+
render: createRender(ChipPicker),
|
|
8
|
+
argTypes: { onToggle: { action: "onToggle" } },
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const Default = {
|
|
12
|
+
args: {
|
|
13
|
+
label: "What do you do?",
|
|
14
|
+
hint: "Pick as many as you like.",
|
|
15
|
+
options: [
|
|
16
|
+
{ value: "photographer", label: "Photographer" },
|
|
17
|
+
{ value: "videographer", label: "Videographer" },
|
|
18
|
+
{ value: "dj", label: "DJ" },
|
|
19
|
+
],
|
|
20
|
+
value: ["videographer"],
|
|
21
|
+
},
|
|
22
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
.iw-chip-picker {
|
|
2
|
+
min-width: 0;
|
|
3
|
+
margin: 0;
|
|
4
|
+
padding: 0;
|
|
5
|
+
border: none;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.iw-chip-picker-legend {
|
|
9
|
+
padding: 0;
|
|
10
|
+
margin-bottom: var(--iw-space-2);
|
|
11
|
+
font-size: var(--iw-font-size-sm);
|
|
12
|
+
font-weight: var(--iw-font-weight-medium);
|
|
13
|
+
color: var(--iw-color-text);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.iw-chip-picker-chips {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-wrap: wrap;
|
|
19
|
+
gap: var(--iw-space-2);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.iw-chip-picker-hint {
|
|
23
|
+
margin: var(--iw-space-2) 0 0;
|
|
24
|
+
font-size: var(--iw-font-size-xs);
|
|
25
|
+
color: var(--iw-color-text-muted, var(--iw-color-text));
|
|
26
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('../../../types/controls/chip-picker').ChipPickerProps} ChipPickerProps
|
|
3
|
+
* @typedef {import('@inglorious/web').TemplateResult} TemplateResult
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { html } from "@inglorious/web"
|
|
7
|
+
import { when } from "@inglorious/web/directives/when"
|
|
8
|
+
|
|
9
|
+
import { Chip } from "../../data-display/chip/index.js"
|
|
10
|
+
import { formatOption } from "../combobox/helpers.js"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Renders a group of toggle chips for picking several options, as a fieldset
|
|
14
|
+
* whose legend is `label`.
|
|
15
|
+
* @param {ChipPickerProps} props
|
|
16
|
+
* @returns {TemplateResult}
|
|
17
|
+
*/
|
|
18
|
+
export function render(props) {
|
|
19
|
+
const {
|
|
20
|
+
label,
|
|
21
|
+
hint,
|
|
22
|
+
options = [],
|
|
23
|
+
value = [],
|
|
24
|
+
isDisabled = false,
|
|
25
|
+
className = "",
|
|
26
|
+
onToggle,
|
|
27
|
+
} = props
|
|
28
|
+
|
|
29
|
+
return html`<fieldset
|
|
30
|
+
class=${["iw-chip-picker", className].filter(Boolean).join(" ")}
|
|
31
|
+
>
|
|
32
|
+
${when(
|
|
33
|
+
label,
|
|
34
|
+
() => html`<legend class="iw-chip-picker-legend">${label}</legend>`,
|
|
35
|
+
)}
|
|
36
|
+
<div class="iw-chip-picker-chips">
|
|
37
|
+
${options.map((option) => {
|
|
38
|
+
const {
|
|
39
|
+
value: optionValue,
|
|
40
|
+
label: optionLabel,
|
|
41
|
+
...rest
|
|
42
|
+
} = formatOption(option)
|
|
43
|
+
return Chip.render({
|
|
44
|
+
children: optionLabel,
|
|
45
|
+
isSelected: value.includes(optionValue),
|
|
46
|
+
isDisabled: isDisabled || Boolean(rest.isDisabled),
|
|
47
|
+
onClick: () => onToggle?.(optionValue),
|
|
48
|
+
})
|
|
49
|
+
})}
|
|
50
|
+
</div>
|
|
51
|
+
${when(hint, () => html`<p class="iw-chip-picker-hint">${hint}</p>`)}
|
|
52
|
+
</fieldset>`
|
|
53
|
+
}
|
|
@@ -29,6 +29,10 @@ export default {
|
|
|
29
29
|
},
|
|
30
30
|
isSearchable: { control: "boolean", description: "Show search input." },
|
|
31
31
|
isClearable: { control: "boolean", description: "Show clear button." },
|
|
32
|
+
isCreatable: {
|
|
33
|
+
control: "boolean",
|
|
34
|
+
description: "Offer to add a searched term that matches no option.",
|
|
35
|
+
},
|
|
32
36
|
isDisabled: { control: "boolean", description: "Disable the control." },
|
|
33
37
|
isLoading: { control: "boolean", description: "Show loading state." },
|
|
34
38
|
searchTerm: { control: "text", description: "Current search filter." },
|
|
@@ -95,6 +99,16 @@ MultiOpen.args = {
|
|
|
95
99
|
isOpen: true,
|
|
96
100
|
}
|
|
97
101
|
|
|
102
|
+
export const Creatable = {}
|
|
103
|
+
Creatable.args = {
|
|
104
|
+
...Default.args,
|
|
105
|
+
isMulti: true,
|
|
106
|
+
isCreatable: true,
|
|
107
|
+
selectedValue: ["it", "Atlantis"],
|
|
108
|
+
searchTerm: "Narnia",
|
|
109
|
+
isOpen: true,
|
|
110
|
+
}
|
|
111
|
+
|
|
98
112
|
export const WithHint = {}
|
|
99
113
|
WithHint.args = {
|
|
100
114
|
...Default.args,
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* @typedef {import('../../../types/controls/select.js').SelectOption} SelectOption
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { formatOption, getOptionValue, visibleOptions } from "./helpers.js"
|
|
7
|
+
|
|
8
|
+
const NONE = -1
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Initialize missing select state defaults.
|
|
@@ -16,7 +18,7 @@ export function create(entity) {
|
|
|
16
18
|
|
|
17
19
|
entity.isOpen ??= false
|
|
18
20
|
entity.searchTerm ??= ""
|
|
19
|
-
entity.focusedIndex ??=
|
|
21
|
+
entity.focusedIndex ??= NONE
|
|
20
22
|
|
|
21
23
|
entity.isMulti ??= false
|
|
22
24
|
entity.selectedValue ??= entity.isMulti ? [] : null
|
|
@@ -38,15 +40,13 @@ export function create(entity) {
|
|
|
38
40
|
export function open(entity) {
|
|
39
41
|
if (entity.isDisabled) return
|
|
40
42
|
entity.isOpen = true
|
|
41
|
-
|
|
42
|
-
const filtered = filterOptions(entity.options, entity.searchTerm)
|
|
43
|
-
entity.focusedIndex = filtered.length ? 0 : -1
|
|
43
|
+
entity.focusedIndex = enabledIndex(visibleOptions(entity), 0, 1)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** @param {SelectEntity} entity */
|
|
47
47
|
export function close(entity) {
|
|
48
48
|
entity.isOpen = false
|
|
49
|
-
entity.focusedIndex =
|
|
49
|
+
entity.focusedIndex = NONE
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/** @param {SelectEntity} entity */
|
|
@@ -60,12 +60,18 @@ export function toggle(entity) {
|
|
|
60
60
|
* @param {SelectEntity} entity
|
|
61
61
|
* @param {SelectOption} option
|
|
62
62
|
*/
|
|
63
|
-
export function optionSelect(entity, option) {
|
|
63
|
+
export function optionSelect(entity, option, api) {
|
|
64
64
|
if (entity.isDisabled) return
|
|
65
65
|
|
|
66
66
|
const normalizedOption = formatOption(option)
|
|
67
|
+
if (normalizedOption.isDisabled) return
|
|
67
68
|
const optionValue = getOptionValue(normalizedOption)
|
|
68
69
|
|
|
70
|
+
if (normalizedOption.isCreated) {
|
|
71
|
+
entity.searchTerm = ""
|
|
72
|
+
entity.focusedIndex = NONE
|
|
73
|
+
}
|
|
74
|
+
|
|
69
75
|
if (entity.isMulti) {
|
|
70
76
|
const values = Array.isArray(entity.selectedValue)
|
|
71
77
|
? entity.selectedValue
|
|
@@ -79,17 +85,24 @@ export function optionSelect(entity, option) {
|
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
entity.selectedValue = values
|
|
88
|
+
notifyChange(entity, api)
|
|
82
89
|
return
|
|
83
90
|
}
|
|
84
91
|
|
|
85
92
|
entity.selectedValue = optionValue
|
|
86
93
|
close(entity)
|
|
94
|
+
notifyChange(entity, api)
|
|
87
95
|
}
|
|
88
96
|
|
|
89
|
-
/**
|
|
90
|
-
|
|
97
|
+
/**
|
|
98
|
+
* @param {SelectEntity} entity
|
|
99
|
+
* @param {unknown} _payload
|
|
100
|
+
* @param {import('@inglorious/web').Api} [api]
|
|
101
|
+
*/
|
|
102
|
+
export function clear(entity, _payload, api) {
|
|
91
103
|
if (entity.isDisabled) return
|
|
92
104
|
entity.selectedValue = entity.isMulti ? [] : null
|
|
105
|
+
notifyChange(entity, api)
|
|
93
106
|
}
|
|
94
107
|
|
|
95
108
|
/**
|
|
@@ -98,32 +111,36 @@ export function clear(entity) {
|
|
|
98
111
|
*/
|
|
99
112
|
export function searchChange(entity, searchTerm) {
|
|
100
113
|
entity.searchTerm = searchTerm
|
|
101
|
-
|
|
102
|
-
entity.focusedIndex = filtered.length ? 0 : -1
|
|
114
|
+
entity.focusedIndex = enabledIndex(visibleOptions(entity), 0, 1)
|
|
103
115
|
}
|
|
104
116
|
|
|
105
117
|
/** @param {SelectEntity} entity */
|
|
106
118
|
export function focusNext(entity) {
|
|
107
|
-
const
|
|
108
|
-
if (
|
|
109
|
-
entity.focusedIndex = Math.min(entity.focusedIndex + 1, filtered.length - 1)
|
|
119
|
+
const next = enabledIndex(visibleOptions(entity), entity.focusedIndex + 1, 1)
|
|
120
|
+
if (next !== NONE) entity.focusedIndex = next
|
|
110
121
|
}
|
|
111
122
|
|
|
112
|
-
/**
|
|
123
|
+
/**
|
|
124
|
+
* Before the first enabled option, focus goes back to the search box.
|
|
125
|
+
* @param {SelectEntity} entity
|
|
126
|
+
*/
|
|
113
127
|
export function focusPrev(entity) {
|
|
114
|
-
entity.focusedIndex =
|
|
128
|
+
entity.focusedIndex = enabledIndex(
|
|
129
|
+
visibleOptions(entity),
|
|
130
|
+
entity.focusedIndex - 1,
|
|
131
|
+
-1,
|
|
132
|
+
)
|
|
115
133
|
}
|
|
116
134
|
|
|
117
135
|
/** @param {SelectEntity} entity */
|
|
118
136
|
export function focusFirst(entity) {
|
|
119
|
-
|
|
120
|
-
if (filtered.length) entity.focusedIndex = 0
|
|
137
|
+
entity.focusedIndex = enabledIndex(visibleOptions(entity), 0, 1)
|
|
121
138
|
}
|
|
122
139
|
|
|
123
140
|
/** @param {SelectEntity} entity */
|
|
124
141
|
export function focusLast(entity) {
|
|
125
|
-
const filtered =
|
|
126
|
-
|
|
142
|
+
const filtered = visibleOptions(entity)
|
|
143
|
+
entity.focusedIndex = enabledIndex(filtered, filtered.length - 1, -1)
|
|
127
144
|
}
|
|
128
145
|
|
|
129
146
|
/**
|
|
@@ -134,3 +151,53 @@ export function focusLast(entity) {
|
|
|
134
151
|
export function focusSet(entity, index) {
|
|
135
152
|
entity.focusedIndex = Number.isInteger(index) ? index : entity.focusedIndex
|
|
136
153
|
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Sets the value from outside, such as a form being filled with saved data.
|
|
157
|
+
* An empty string, as an empty form field holds, is no value. Unlike a
|
|
158
|
+
* choice, it does not notify `changeEvent`.
|
|
159
|
+
* @param {SelectEntity} entity
|
|
160
|
+
* @param {string | number | (string | number)[] | null} value
|
|
161
|
+
*/
|
|
162
|
+
export function valueSet(entity, value) {
|
|
163
|
+
if (entity.isMulti) entity.selectedValue = [...(value ?? [])]
|
|
164
|
+
else entity.selectedValue = value === "" ? null : (value ?? null)
|
|
165
|
+
entity.searchTerm = ""
|
|
166
|
+
entity.focusedIndex = NONE
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Replaces the options, such as when they depend on another field.
|
|
171
|
+
* @param {SelectEntity} entity
|
|
172
|
+
* @param {SelectOption[]} options
|
|
173
|
+
*/
|
|
174
|
+
export function optionsSet(entity, options) {
|
|
175
|
+
entity.options = (options ?? []).map(formatOption)
|
|
176
|
+
entity.searchTerm = ""
|
|
177
|
+
entity.focusedIndex = NONE
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Shows a validation message. The combobox is its own entity, so the form
|
|
182
|
+
* that validates it hands the message over.
|
|
183
|
+
* @param {SelectEntity} entity
|
|
184
|
+
* @param {string | null} error
|
|
185
|
+
*/
|
|
186
|
+
export function errorSet(entity, error) {
|
|
187
|
+
entity.error = error ?? null
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function notifyChange(entity, api) {
|
|
191
|
+
if (!entity.changeEvent || !api) return
|
|
192
|
+
const value = entity.isMulti
|
|
193
|
+
? [...entity.selectedValue]
|
|
194
|
+
: entity.selectedValue
|
|
195
|
+
api.notify(entity.changeEvent, { ...entity.changePayload, value })
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function enabledIndex(options, start, step) {
|
|
199
|
+
for (let index = start; index >= 0 && index < options.length; index += step) {
|
|
200
|
+
if (!formatOption(options[index]).isDisabled) return index
|
|
201
|
+
}
|
|
202
|
+
return NONE
|
|
203
|
+
}
|
|
@@ -45,10 +45,45 @@ export function filterOptions(options, searchTerm) {
|
|
|
45
45
|
|
|
46
46
|
const searchLower = String(searchTerm).toLowerCase().trim()
|
|
47
47
|
|
|
48
|
-
return options.filter((option) =>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
return options.filter((option) =>
|
|
49
|
+
optionTexts(option).some((text) => text.includes(searchLower)),
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The options the dropdown lists: those matching the search, then, on a
|
|
55
|
+
* creatable combobox, one to add the typed term unless it is already known.
|
|
56
|
+
* @param {{ options?: SelectOption[], searchTerm?: string, selectedValue?: string|number|(string|number)[]|null, isCreatable?: boolean, formatCreateLabel?: (term: string) => string }} props
|
|
57
|
+
* @returns {SelectOption[]}
|
|
58
|
+
*/
|
|
59
|
+
export function visibleOptions({
|
|
60
|
+
options = [],
|
|
61
|
+
searchTerm,
|
|
62
|
+
selectedValue,
|
|
63
|
+
isCreatable = false,
|
|
64
|
+
formatCreateLabel = (term) => `Add "${term}"`,
|
|
65
|
+
}) {
|
|
66
|
+
const filtered = filterOptions(options, searchTerm)
|
|
67
|
+
const term = String(searchTerm ?? "").trim()
|
|
68
|
+
if (!isCreatable || !term) return filtered
|
|
69
|
+
|
|
70
|
+
const key = term.toLowerCase()
|
|
71
|
+
const isKnown =
|
|
72
|
+
[selectedValue]
|
|
73
|
+
.flat()
|
|
74
|
+
.some((value) => String(value).toLowerCase() === key) ||
|
|
75
|
+
options.some(
|
|
76
|
+
(option) =>
|
|
77
|
+
!formatOption(option).isDisabled &&
|
|
78
|
+
(optionTexts(option).includes(key) ||
|
|
79
|
+
String(getOptionValue(option)).toLowerCase() === key),
|
|
80
|
+
)
|
|
81
|
+
if (isKnown) return filtered
|
|
82
|
+
|
|
83
|
+
return [
|
|
84
|
+
...filtered,
|
|
85
|
+
{ value: term, label: formatCreateLabel(term), isCreated: true },
|
|
86
|
+
]
|
|
52
87
|
}
|
|
53
88
|
|
|
54
89
|
/**
|
|
@@ -89,3 +124,14 @@ export function formatOption(option) {
|
|
|
89
124
|
|
|
90
125
|
return { value: String(option), label: String(option) }
|
|
91
126
|
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* What a search looks through: the label and the option's keywords, lowercased.
|
|
130
|
+
* @param {SelectOption} option
|
|
131
|
+
* @returns {string[]}
|
|
132
|
+
*/
|
|
133
|
+
function optionTexts(option) {
|
|
134
|
+
return [getOptionLabel(option), ...(option?.keywords ?? [])].map((text) =>
|
|
135
|
+
text.toLowerCase(),
|
|
136
|
+
)
|
|
137
|
+
}
|
|
@@ -14,11 +14,11 @@ import { Chip } from "../../data-display/chip/index.js"
|
|
|
14
14
|
import { fallbackLabel } from "../../shared/fallback-label.js"
|
|
15
15
|
import { syncPopover } from "../../shared/top-layer.js"
|
|
16
16
|
import {
|
|
17
|
-
filterOptions,
|
|
18
17
|
formatOption,
|
|
19
18
|
getOptionLabel,
|
|
20
19
|
getOptionValue,
|
|
21
20
|
isOptionSelected,
|
|
21
|
+
visibleOptions,
|
|
22
22
|
} from "./helpers.js"
|
|
23
23
|
|
|
24
24
|
const NO_FOCUSED_INDEX = -1
|
|
@@ -234,18 +234,20 @@ export const Combobox = {
|
|
|
234
234
|
*/
|
|
235
235
|
renderMultiValueTag(props, value) {
|
|
236
236
|
const { id, options, size, onOptionSelect, onChange } = props
|
|
237
|
+
const option = options.find((opt) => getOptionValue(opt) === value) ?? value
|
|
237
238
|
|
|
238
|
-
const option = options.find((opt) => getOptionValue(opt) === value)
|
|
239
239
|
const tagId = id ? `${id}-tag-${String(value)}` : undefined
|
|
240
240
|
const chipSize = size === "lg" ? "md" : "sm"
|
|
241
241
|
|
|
242
242
|
return html`<span
|
|
243
243
|
class="iw-combobox-multi-value-tag"
|
|
244
|
-
@click=${(event) =>
|
|
244
|
+
@click=${(event) => {
|
|
245
|
+
if (event.target.closest(".iw-chip-remove")) event.stopPropagation()
|
|
246
|
+
}}
|
|
245
247
|
>
|
|
246
248
|
${Chip.render({
|
|
247
249
|
id: tagId,
|
|
248
|
-
children:
|
|
250
|
+
children: getOptionLabel(option),
|
|
249
251
|
isRemovable: true,
|
|
250
252
|
size: chipSize,
|
|
251
253
|
shape: "rounded",
|
|
@@ -269,9 +271,9 @@ export const Combobox = {
|
|
|
269
271
|
* @returns {TemplateResult}
|
|
270
272
|
*/
|
|
271
273
|
renderDropdown(props) {
|
|
272
|
-
const { id,
|
|
274
|
+
const { id, isSearchable, isLoading } = props
|
|
273
275
|
|
|
274
|
-
const filteredOptions =
|
|
276
|
+
const filteredOptions = visibleOptions(props)
|
|
275
277
|
|
|
276
278
|
return html`<div
|
|
277
279
|
id=${id ? `${id}-control-listbox` : nothing}
|
|
@@ -328,9 +330,7 @@ export const Combobox = {
|
|
|
328
330
|
* @returns {TemplateResult}
|
|
329
331
|
*/
|
|
330
332
|
renderOptions(props) {
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
const filteredOptions = filterOptions(options, searchTerm)
|
|
333
|
+
const filteredOptions = visibleOptions(props)
|
|
334
334
|
|
|
335
335
|
return html`<div class="iw-combobox-dropdown-options">
|
|
336
336
|
${repeat(
|
|
@@ -380,7 +380,7 @@ export const Combobox = {
|
|
|
380
380
|
@mouseenter=${() => onFocusSet?.(index)}
|
|
381
381
|
>
|
|
382
382
|
${when(
|
|
383
|
-
isMulti,
|
|
383
|
+
isMulti && !normalized.isCreated,
|
|
384
384
|
() =>
|
|
385
385
|
html`<input
|
|
386
386
|
type="checkbox"
|
|
@@ -428,7 +428,8 @@ export const Combobox = {
|
|
|
428
428
|
function handleControlKeyDown(props, event) {
|
|
429
429
|
const { isDisabled, isOpen, onToggle } = props
|
|
430
430
|
|
|
431
|
-
|
|
431
|
+
// The keys of a tag's remove button or the clear button are their own.
|
|
432
|
+
if (isDisabled || event.target.closest?.("button")) return
|
|
432
433
|
|
|
433
434
|
if (isOpen) {
|
|
434
435
|
handleKeyDown(props, event)
|
|
@@ -450,8 +451,6 @@ function handleControlKeyDown(props, event) {
|
|
|
450
451
|
*/
|
|
451
452
|
function handleKeyDown(props, event) {
|
|
452
453
|
const {
|
|
453
|
-
options,
|
|
454
|
-
searchTerm,
|
|
455
454
|
focusedIndex,
|
|
456
455
|
onFocusFirst,
|
|
457
456
|
onFocusPrev,
|
|
@@ -462,7 +461,7 @@ function handleKeyDown(props, event) {
|
|
|
462
461
|
onClose,
|
|
463
462
|
} = props
|
|
464
463
|
|
|
465
|
-
const filteredOptions =
|
|
464
|
+
const filteredOptions = visibleOptions(props)
|
|
466
465
|
|
|
467
466
|
switch (event.key) {
|
|
468
467
|
case "ArrowDown":
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
@import "./button/style.css";
|
|
2
1
|
@import "./button-group/style.css";
|
|
2
|
+
@import "./button/style.css";
|
|
3
3
|
@import "./checkbox/style.css";
|
|
4
|
+
@import "./chip-picker/style.css";
|
|
4
5
|
@import "./combobox/style.css";
|
|
5
6
|
@import "./fab/style.css";
|
|
7
|
+
@import "./file-upload/style.css";
|
|
6
8
|
@import "./icon-button/style.css";
|
|
7
9
|
@import "./image-cropper/style.css";
|
|
8
10
|
@import "./input/style.css";
|
|
@@ -12,4 +14,3 @@
|
|
|
12
14
|
@import "./slider/style.css";
|
|
13
15
|
@import "./switch/style.css";
|
|
14
16
|
@import "./textarea/style.css";
|
|
15
|
-
@import "./file-upload/style.css";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createRender } from "../../stories/notify-story.js"
|
|
2
|
+
import { Lightbox } from "."
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: "Data Display/Lightbox",
|
|
6
|
+
tags: ["autodocs"],
|
|
7
|
+
render: createRender(Lightbox),
|
|
8
|
+
argTypes: {
|
|
9
|
+
index: { control: "number" },
|
|
10
|
+
onStep: { action: "onStep" },
|
|
11
|
+
onClose: { action: "onClose" },
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const Default = {
|
|
16
|
+
args: {
|
|
17
|
+
isOpen: true,
|
|
18
|
+
index: 0,
|
|
19
|
+
images: [
|
|
20
|
+
"https://picsum.photos/id/1011/1200/800",
|
|
21
|
+
"https://picsum.photos/id/1015/1200/800",
|
|
22
|
+
"https://picsum.photos/id/1016/800/1200",
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
}
|