@agnos-ui/core 0.0.1-alpha.3 → 0.0.1-alpha.5
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/{accordion.d.ts → components/accordion/accordion.d.ts} +13 -4
- package/{accordion.js → components/accordion/accordion.js} +17 -11
- package/components/alert/alert.d.ts +32 -0
- package/components/alert/alert.js +23 -0
- package/{alert.d.ts → components/alert/common.d.ts} +17 -21
- package/{alert.js → components/alert/common.js} +12 -12
- package/{modal → components/modal}/modal.d.ts +28 -21
- package/{modal → components/modal}/modal.js +39 -13
- package/{pagination.d.ts → components/pagination/pagination.d.ts} +3 -3
- package/{pagination.js → components/pagination/pagination.js} +6 -4
- package/{progressbar.d.ts → components/progressbar/progressbar.d.ts} +2 -3
- package/{progressbar.js → components/progressbar/progressbar.js} +6 -6
- package/{rating.d.ts → components/rating/rating.d.ts} +2 -3
- package/{rating.js → components/rating/rating.js} +6 -9
- package/components/select/select.d.ts +337 -0
- package/components/select/select.js +266 -0
- package/components/slider/slider.d.ts +239 -0
- package/components/slider/slider.js +389 -0
- package/config.d.ts +13 -8
- package/config.js +3 -3
- package/index.d.ts +23 -12
- package/index.js +29 -12
- package/package.json +32 -4
- package/services/extendWidget.d.ts +23 -0
- package/{extendWidget.js → services/extendWidget.js} +8 -1
- package/services/floatingUI.d.ts +48 -0
- package/services/floatingUI.js +97 -0
- package/services/focustrack.js +1 -1
- package/services/intersection.d.ts +1 -1
- package/services/intersection.js +2 -2
- package/services/navManager.d.ts +83 -1
- package/services/navManager.js +153 -37
- package/services/portal.js +8 -4
- package/services/siblingsInert.d.ts +2 -1
- package/services/siblingsInert.js +2 -2
- package/{transitions → services/transitions}/baseTransitions.d.ts +1 -2
- package/{transitions → services/transitions}/baseTransitions.js +7 -10
- package/services/transitions/bootstrap/collapse.d.ts +2 -0
- package/services/transitions/bootstrap/fade.d.ts +1 -0
- package/services/transitions/bootstrap.d.ts +2 -0
- package/services/transitions/bootstrap.js +2 -0
- package/{transitions → services/transitions}/collapse.js +1 -1
- package/{transitions → services/transitions}/cssTransitions.js +2 -4
- package/{transitions → services/transitions}/simpleClassTransition.js +1 -1
- package/types.d.ts +37 -4
- package/types.js +1 -0
- package/{services/directiveUtils.js → utils/directive.js} +1 -1
- package/{services → utils/internal}/checks.d.ts +12 -0
- package/{services → utils/internal}/checks.js +12 -0
- package/utils/internal/dom.d.ts +13 -0
- package/utils/internal/dom.js +49 -0
- package/utils/internal/isFocusable.d.ts +9 -0
- package/utils/internal/isFocusable.js +35 -0
- package/utils/internal/math.d.ts +5 -0
- package/utils/internal/math.js +13 -0
- package/utils/internal/promise.d.ts +31 -0
- package/utils/internal/promise.js +113 -0
- package/{modal → utils/internal}/scrollbars.js +1 -1
- package/utils/internal/textDirection.d.ts +1 -0
- package/utils/internal/textDirection.js +1 -0
- package/utils/internal/traversal.d.ts +54 -0
- package/utils/internal/traversal.js +105 -0
- package/{services → utils}/stores.d.ts +11 -35
- package/{services → utils}/stores.js +21 -19
- package/utils/writables.d.ts +26 -0
- package/utils/writables.js +66 -0
- package/extendWidget.d.ts +0 -3
- package/select.d.ts +0 -196
- package/select.js +0 -240
- package/services/index.d.ts +0 -8
- package/services/index.js +0 -8
- package/services/writables.d.ts +0 -8
- package/services/writables.js +0 -30
- package/transitions/bootstrap/collapse.d.ts +0 -2
- package/transitions/bootstrap/fade.d.ts +0 -1
- package/transitions/bootstrap/index.d.ts +0 -2
- package/transitions/bootstrap/index.js +0 -2
- package/transitions/index.d.ts +0 -5
- package/transitions/index.js +0 -5
- package/transitions/utils.d.ts +0 -20
- package/transitions/utils.js +0 -83
- /package/{commonProps.d.ts → components/commonProps.d.ts} +0 -0
- /package/{commonProps.js → components/commonProps.js} +0 -0
- /package/{pagination.utils.d.ts → components/pagination/bootstrap.d.ts} +0 -0
- /package/{pagination.utils.js → components/pagination/bootstrap.js} +0 -0
- /package/{transitions → services/transitions}/bootstrap/collapse.js +0 -0
- /package/{transitions → services/transitions}/bootstrap/fade.js +0 -0
- /package/{transitions → services/transitions}/collapse.d.ts +0 -0
- /package/{transitions → services/transitions}/cssTransitions.d.ts +0 -0
- /package/{transitions → services/transitions}/simpleClassTransition.d.ts +0 -0
- /package/{services/directiveUtils.d.ts → utils/directive.d.ts} +0 -0
- /package/{utils.d.ts → utils/internal/func.d.ts} +0 -0
- /package/{utils.js → utils/internal/func.js} +0 -0
- /package/{modal → utils/internal}/scrollbars.d.ts +0 -0
- /package/{services/sortUtils.d.ts → utils/internal/sort.d.ts} +0 -0
- /package/{services/sortUtils.js → utils/internal/sort.js} +0 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import type { Placement } from '@floating-ui/dom';
|
|
2
|
+
import type { FloatingUI } from '../../services/floatingUI';
|
|
3
|
+
import type { HasFocus } from '../../services/focustrack';
|
|
4
|
+
import type { PropsConfig, SlotContent, Widget, WidgetSlotContext } from '../../types';
|
|
5
|
+
import type { WidgetsCommonPropsAndState } from '../commonProps';
|
|
6
|
+
/**
|
|
7
|
+
* A type for the slot context of the pagination widget
|
|
8
|
+
*/
|
|
9
|
+
export type SelectContext<Item> = WidgetSlotContext<SelectWidget<Item>>;
|
|
10
|
+
export interface SelectItemContext<Item> extends SelectContext<Item> {
|
|
11
|
+
/**
|
|
12
|
+
* Contextual data related to an item
|
|
13
|
+
*/
|
|
14
|
+
itemContext: ItemContext<Item>;
|
|
15
|
+
}
|
|
16
|
+
export interface SelectCommonPropsAndState<Item> extends WidgetsCommonPropsAndState {
|
|
17
|
+
/**
|
|
18
|
+
* id used for the input inside the select
|
|
19
|
+
*/
|
|
20
|
+
id: string | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* aria-label used for the input inside the select
|
|
23
|
+
*/
|
|
24
|
+
ariaLabel: string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* List of selected item ids
|
|
27
|
+
*/
|
|
28
|
+
selected: Item[];
|
|
29
|
+
/**
|
|
30
|
+
* Filtered text to be display in the filter input
|
|
31
|
+
*/
|
|
32
|
+
filterText: string;
|
|
33
|
+
/**
|
|
34
|
+
* true if the select is disabled
|
|
35
|
+
*/
|
|
36
|
+
disabled: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* true if the select is open
|
|
39
|
+
*/
|
|
40
|
+
open: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Class to be added on the dropdown menu container
|
|
43
|
+
*/
|
|
44
|
+
menuClassName: string;
|
|
45
|
+
/**
|
|
46
|
+
* Class to be added on menu items
|
|
47
|
+
*/
|
|
48
|
+
menuItemClassName: string;
|
|
49
|
+
/**
|
|
50
|
+
* Class to be added on selected items (displayed in the input zone)
|
|
51
|
+
*/
|
|
52
|
+
badgeClassName: string;
|
|
53
|
+
/**
|
|
54
|
+
* true if a loading process is being done
|
|
55
|
+
*/
|
|
56
|
+
loading: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* The template to override the way each badge on the left of the input is displayed.
|
|
59
|
+
* This define the content of the badge inside the badge container.
|
|
60
|
+
*/
|
|
61
|
+
slotBadgeLabel: SlotContent<SelectItemContext<Item>>;
|
|
62
|
+
/**
|
|
63
|
+
* The template to override the way each item is displayed in the list.
|
|
64
|
+
* This define the content of the badge inside the badge container.
|
|
65
|
+
*/
|
|
66
|
+
slotItem: SlotContent<SelectItemContext<Item>>;
|
|
67
|
+
}
|
|
68
|
+
export interface SelectProps<T> extends SelectCommonPropsAndState<T> {
|
|
69
|
+
/**
|
|
70
|
+
* List of available items for the dropdown
|
|
71
|
+
*/
|
|
72
|
+
items: T[];
|
|
73
|
+
/**
|
|
74
|
+
* List of allowed placements for the dropdown.
|
|
75
|
+
* This refers to the [allowedPlacements from floating UI](https://floating-ui.com/docs/autoPlacement#allowedplacements), given the different [Placement possibilities](https://floating-ui.com/docs/computePosition#placement).
|
|
76
|
+
*/
|
|
77
|
+
allowedPlacements: Placement[];
|
|
78
|
+
/**
|
|
79
|
+
* Custom function to get the id of an item
|
|
80
|
+
* By default, the item is returned
|
|
81
|
+
*/
|
|
82
|
+
itemIdFn(item: T): string;
|
|
83
|
+
/**
|
|
84
|
+
* Callback called dropdown open state change
|
|
85
|
+
* @param isOpen - updated open state
|
|
86
|
+
*/
|
|
87
|
+
onOpenChange(isOpen: boolean): void;
|
|
88
|
+
/**
|
|
89
|
+
* Callback called when the text filter change
|
|
90
|
+
* @param text - Filtered text
|
|
91
|
+
*/
|
|
92
|
+
onFilterTextChange(text: string): void;
|
|
93
|
+
/**
|
|
94
|
+
* Callback called when the selection change
|
|
95
|
+
*/
|
|
96
|
+
onSelectedChange(selected: T[]): void;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Item representation built from the items provided in parameters
|
|
100
|
+
*/
|
|
101
|
+
export interface ItemContext<T> {
|
|
102
|
+
/**
|
|
103
|
+
* Original item given in the parameters
|
|
104
|
+
*/
|
|
105
|
+
item: T;
|
|
106
|
+
/**
|
|
107
|
+
* Unique id to identify the item
|
|
108
|
+
*/
|
|
109
|
+
id: string;
|
|
110
|
+
/**
|
|
111
|
+
* Specify if the item is checked
|
|
112
|
+
*/
|
|
113
|
+
selected: boolean;
|
|
114
|
+
}
|
|
115
|
+
export interface SelectState<Item> extends SelectCommonPropsAndState<Item> {
|
|
116
|
+
/**
|
|
117
|
+
* List of item contexts, to be displayed in the menu
|
|
118
|
+
*/
|
|
119
|
+
visibleItems: ItemContext<Item>[];
|
|
120
|
+
/**
|
|
121
|
+
/**
|
|
122
|
+
* List of selected items to be display
|
|
123
|
+
*/
|
|
124
|
+
selectedContexts: ItemContext<Item>[];
|
|
125
|
+
/**
|
|
126
|
+
* Highlighted item context.
|
|
127
|
+
* It is designed to define the highlighted item in the dropdown menu
|
|
128
|
+
*/
|
|
129
|
+
highlighted: ItemContext<Item> | undefined;
|
|
130
|
+
/**
|
|
131
|
+
* Current placement of the dropdown
|
|
132
|
+
*/
|
|
133
|
+
placement: Placement | undefined;
|
|
134
|
+
}
|
|
135
|
+
export interface SelectApi<Item> {
|
|
136
|
+
/**
|
|
137
|
+
* Clear all the selected items
|
|
138
|
+
*/
|
|
139
|
+
clear(): void;
|
|
140
|
+
/**
|
|
141
|
+
* Clear the filter text
|
|
142
|
+
*/
|
|
143
|
+
clearText(): void;
|
|
144
|
+
/**
|
|
145
|
+
* Highlight the given item, if there is a corresponding match among the visible list
|
|
146
|
+
*/
|
|
147
|
+
highlight(item: Item): void;
|
|
148
|
+
/**
|
|
149
|
+
* Highlight the first item among the visible list
|
|
150
|
+
*/
|
|
151
|
+
highlightFirst(): void;
|
|
152
|
+
/**
|
|
153
|
+
* Highlight the previous item among the visible list
|
|
154
|
+
* Loop to the last item if needed
|
|
155
|
+
*/
|
|
156
|
+
highlightPrevious(): void;
|
|
157
|
+
/**
|
|
158
|
+
* Highlight the next item among the visible list.
|
|
159
|
+
* Loop to the first item if needed
|
|
160
|
+
*/
|
|
161
|
+
highlightNext(): void;
|
|
162
|
+
/**
|
|
163
|
+
* Highlight the last item among the visible list
|
|
164
|
+
*/
|
|
165
|
+
highlightLast(): void;
|
|
166
|
+
/**
|
|
167
|
+
* Focus the provided item among the selected list.
|
|
168
|
+
* The focus feature is designed to know what item must be focused in the UI, i.e. among the badge elements.
|
|
169
|
+
*/
|
|
170
|
+
focus(item: Item): void;
|
|
171
|
+
/**
|
|
172
|
+
* Focus the first element
|
|
173
|
+
*/
|
|
174
|
+
focusFirst(): void;
|
|
175
|
+
/**
|
|
176
|
+
* Focus the previous element. If no element was focused before the call, nothing happens.
|
|
177
|
+
*/
|
|
178
|
+
focusPrevious(): void;
|
|
179
|
+
/**
|
|
180
|
+
* Focus the next element. If no element was focused before the call, nothing happens.
|
|
181
|
+
*/
|
|
182
|
+
focusNext(): void;
|
|
183
|
+
/**
|
|
184
|
+
* Focus the last element. If no element was focused before the call, nothing happens.
|
|
185
|
+
*/
|
|
186
|
+
focusLast(): void;
|
|
187
|
+
/**
|
|
188
|
+
* Select the provided item.
|
|
189
|
+
* The selected list is used to
|
|
190
|
+
* @param item - the item to select
|
|
191
|
+
*/
|
|
192
|
+
select(item: Item): void;
|
|
193
|
+
/**
|
|
194
|
+
* Unselect the provided item.
|
|
195
|
+
* @param item - the item to unselect
|
|
196
|
+
*/
|
|
197
|
+
unselect(item: Item): void;
|
|
198
|
+
/**
|
|
199
|
+
* Toggle the selection of an item
|
|
200
|
+
* @param item - the item to toggle
|
|
201
|
+
* @param selected - an optional boolean to enforce the selected/unselected state instead of toggling
|
|
202
|
+
*/
|
|
203
|
+
toggleItem(item: Item, selected?: boolean): void;
|
|
204
|
+
/**
|
|
205
|
+
* open the select
|
|
206
|
+
*/
|
|
207
|
+
open(): void;
|
|
208
|
+
/**
|
|
209
|
+
* close the select
|
|
210
|
+
*/
|
|
211
|
+
close(): void;
|
|
212
|
+
/**
|
|
213
|
+
* Toggle the dropdown menu
|
|
214
|
+
* @param isOpen - If specified, set the menu in the defined state.
|
|
215
|
+
*/
|
|
216
|
+
toggle(isOpen?: boolean): void;
|
|
217
|
+
}
|
|
218
|
+
export interface SelectDirectives {
|
|
219
|
+
/**
|
|
220
|
+
* Directive to be used in the input group and the menu containers
|
|
221
|
+
*/
|
|
222
|
+
hasFocusDirective: HasFocus['directive'];
|
|
223
|
+
/**
|
|
224
|
+
* Directive that enables dynamic positioning of menu element
|
|
225
|
+
*/
|
|
226
|
+
floatingDirective: FloatingUI['directives']['floatingDirective'];
|
|
227
|
+
/**
|
|
228
|
+
* A directive to be applied to the input group element serves as the base for menu positioning
|
|
229
|
+
*/
|
|
230
|
+
referenceDirective: FloatingUI['directives']['referenceDirective'];
|
|
231
|
+
}
|
|
232
|
+
export interface SelectActions {
|
|
233
|
+
/**
|
|
234
|
+
* Method to be plugged to on the 'input' event. The input text will be used as the filter text.
|
|
235
|
+
*/
|
|
236
|
+
onInput: (e: {
|
|
237
|
+
target: any;
|
|
238
|
+
}) => void;
|
|
239
|
+
/**
|
|
240
|
+
* Method to be plugged to on an keydown event, in order to control the keyboard interactions with the highlighted item.
|
|
241
|
+
* It manages arrow keys to move the highlighted item, or enter to toggle the item.
|
|
242
|
+
*/
|
|
243
|
+
onInputKeydown: (e: any) => void;
|
|
244
|
+
}
|
|
245
|
+
export type SelectWidget<Item> = Widget<SelectProps<Item>, SelectState<Item>, SelectApi<Item>, SelectActions, SelectDirectives>;
|
|
246
|
+
export declare const defaultConfig: SelectProps<any>;
|
|
247
|
+
/**
|
|
248
|
+
* Returns a shallow copy of the default select config.
|
|
249
|
+
* @returns a copy of the default config
|
|
250
|
+
*/
|
|
251
|
+
export declare function getSelectDefaultConfig(): {
|
|
252
|
+
/**
|
|
253
|
+
* List of available items for the dropdown
|
|
254
|
+
*/
|
|
255
|
+
items: any[];
|
|
256
|
+
/**
|
|
257
|
+
* List of allowed placements for the dropdown.
|
|
258
|
+
* This refers to the [allowedPlacements from floating UI](https://floating-ui.com/docs/autoPlacement#allowedplacements), given the different [Placement possibilities](https://floating-ui.com/docs/computePosition#placement).
|
|
259
|
+
*/
|
|
260
|
+
allowedPlacements: Placement[];
|
|
261
|
+
/**
|
|
262
|
+
* Custom function to get the id of an item
|
|
263
|
+
* By default, the item is returned
|
|
264
|
+
*/
|
|
265
|
+
itemIdFn(item: any): string;
|
|
266
|
+
/**
|
|
267
|
+
* Callback called dropdown open state change
|
|
268
|
+
* @param isOpen - updated open state
|
|
269
|
+
*/
|
|
270
|
+
onOpenChange(isOpen: boolean): void;
|
|
271
|
+
/**
|
|
272
|
+
* Callback called when the text filter change
|
|
273
|
+
* @param text - Filtered text
|
|
274
|
+
*/
|
|
275
|
+
onFilterTextChange(text: string): void;
|
|
276
|
+
/**
|
|
277
|
+
* Callback called when the selection change
|
|
278
|
+
*/
|
|
279
|
+
onSelectedChange(selected: any[]): void;
|
|
280
|
+
/**
|
|
281
|
+
* id used for the input inside the select
|
|
282
|
+
*/
|
|
283
|
+
id: string | undefined;
|
|
284
|
+
/**
|
|
285
|
+
* aria-label used for the input inside the select
|
|
286
|
+
*/
|
|
287
|
+
ariaLabel: string | undefined;
|
|
288
|
+
/**
|
|
289
|
+
* List of selected item ids
|
|
290
|
+
*/
|
|
291
|
+
selected: any[];
|
|
292
|
+
/**
|
|
293
|
+
* Filtered text to be display in the filter input
|
|
294
|
+
*/
|
|
295
|
+
filterText: string;
|
|
296
|
+
/**
|
|
297
|
+
* true if the select is disabled
|
|
298
|
+
*/
|
|
299
|
+
disabled: boolean;
|
|
300
|
+
/**
|
|
301
|
+
* true if the select is open
|
|
302
|
+
*/
|
|
303
|
+
open: boolean;
|
|
304
|
+
/**
|
|
305
|
+
* Class to be added on the dropdown menu container
|
|
306
|
+
*/
|
|
307
|
+
menuClassName: string;
|
|
308
|
+
/**
|
|
309
|
+
* Class to be added on menu items
|
|
310
|
+
*/
|
|
311
|
+
menuItemClassName: string;
|
|
312
|
+
/**
|
|
313
|
+
* Class to be added on selected items (displayed in the input zone)
|
|
314
|
+
*/
|
|
315
|
+
badgeClassName: string;
|
|
316
|
+
/**
|
|
317
|
+
* true if a loading process is being done
|
|
318
|
+
*/
|
|
319
|
+
loading: boolean;
|
|
320
|
+
/**
|
|
321
|
+
* The template to override the way each badge on the left of the input is displayed.
|
|
322
|
+
* This define the content of the badge inside the badge container.
|
|
323
|
+
*/
|
|
324
|
+
slotBadgeLabel: SlotContent<SelectItemContext<any>>;
|
|
325
|
+
/**
|
|
326
|
+
* The template to override the way each item is displayed in the list.
|
|
327
|
+
* This define the content of the badge inside the badge container.
|
|
328
|
+
*/
|
|
329
|
+
slotItem: SlotContent<SelectItemContext<any>>;
|
|
330
|
+
className: string;
|
|
331
|
+
};
|
|
332
|
+
/**
|
|
333
|
+
* Create a SelectWidget with given config props
|
|
334
|
+
* @param config - an optional alert config
|
|
335
|
+
* @returns a SelectWidget
|
|
336
|
+
*/
|
|
337
|
+
export declare function createSelect<Item>(config?: PropsConfig<SelectProps<Item>>): SelectWidget<Item>;
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { asWritable, computed, writable } from '@amadeus-it-group/tansu';
|
|
2
|
+
import { autoPlacement, offset, size } from '@floating-ui/dom';
|
|
3
|
+
import { createFloatingUI } from '../../services/floatingUI';
|
|
4
|
+
import { createHasFocus } from '../../services/focustrack';
|
|
5
|
+
import { noop } from '../../utils/internal/func';
|
|
6
|
+
import { bindableDerived, stateStores, writablesForProps } from '../../utils/stores';
|
|
7
|
+
const defaultItemId = (item) => '' + item;
|
|
8
|
+
export const defaultConfig = {
|
|
9
|
+
id: undefined,
|
|
10
|
+
ariaLabel: 'Select',
|
|
11
|
+
open: false,
|
|
12
|
+
disabled: false,
|
|
13
|
+
items: [],
|
|
14
|
+
filterText: '',
|
|
15
|
+
loading: false,
|
|
16
|
+
selected: [],
|
|
17
|
+
itemIdFn: defaultItemId,
|
|
18
|
+
onOpenChange: noop,
|
|
19
|
+
onFilterTextChange: noop,
|
|
20
|
+
onSelectedChange: noop,
|
|
21
|
+
allowedPlacements: ['bottom-start', 'top-start', 'bottom-end', 'top-end'],
|
|
22
|
+
className: '',
|
|
23
|
+
menuClassName: '',
|
|
24
|
+
menuItemClassName: '',
|
|
25
|
+
badgeClassName: '',
|
|
26
|
+
slotBadgeLabel: ({ itemContext }) => itemContext.item,
|
|
27
|
+
slotItem: ({ itemContext }) => itemContext.item,
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Returns a shallow copy of the default select config.
|
|
31
|
+
* @returns a copy of the default config
|
|
32
|
+
*/
|
|
33
|
+
export function getSelectDefaultConfig() {
|
|
34
|
+
return { ...defaultConfig };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Create a SelectWidget with given config props
|
|
38
|
+
* @param config - an optional alert config
|
|
39
|
+
* @returns a SelectWidget
|
|
40
|
+
*/
|
|
41
|
+
export function createSelect(config) {
|
|
42
|
+
// Props
|
|
43
|
+
const [{ open$: _dirtyOpen$, filterText$: _dirtyFilterText$, items$, itemIdFn$, onOpenChange$, onFilterTextChange$, onSelectedChange$, allowedPlacements$, ...stateProps }, patch,] = writablesForProps(defaultConfig, config);
|
|
44
|
+
const { selected$ } = stateProps;
|
|
45
|
+
const filterText$ = bindableDerived(onFilterTextChange$, [_dirtyFilterText$]);
|
|
46
|
+
const { hasFocus$, directive: hasFocusDirective } = createHasFocus();
|
|
47
|
+
const open$ = bindableDerived(onOpenChange$, [_dirtyOpen$, hasFocus$], ([_dirtyOpen, hasFocus]) => _dirtyOpen && hasFocus);
|
|
48
|
+
const selectedContextsMap$ = computed(() => {
|
|
49
|
+
const selectedItemsContext = new Map();
|
|
50
|
+
const itemIdFn = itemIdFn$();
|
|
51
|
+
for (const item of selected$()) {
|
|
52
|
+
const id = itemIdFn(item);
|
|
53
|
+
selectedItemsContext.set(id, {
|
|
54
|
+
item,
|
|
55
|
+
id: itemIdFn(item),
|
|
56
|
+
selected: true,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return selectedItemsContext;
|
|
60
|
+
});
|
|
61
|
+
const selectedContexts$ = computed(() => [...selectedContextsMap$().values()]);
|
|
62
|
+
const highlightedIndex$ = (function () {
|
|
63
|
+
const store = writable(0);
|
|
64
|
+
return asWritable(store, (index) => {
|
|
65
|
+
const { length } = visibleItems$();
|
|
66
|
+
if (index != undefined) {
|
|
67
|
+
if (!length) {
|
|
68
|
+
index = undefined;
|
|
69
|
+
}
|
|
70
|
+
else if (index < 0) {
|
|
71
|
+
index = length - 1;
|
|
72
|
+
}
|
|
73
|
+
else if (index >= length) {
|
|
74
|
+
index = 0;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
store.set(index);
|
|
78
|
+
});
|
|
79
|
+
})();
|
|
80
|
+
const itemContexts$ = computed(() => {
|
|
81
|
+
const itemContexts = new Map();
|
|
82
|
+
if (open$()) {
|
|
83
|
+
const selectedContextsMap = selectedContextsMap$();
|
|
84
|
+
const itemIdFn = itemIdFn$();
|
|
85
|
+
for (const item of items$()) {
|
|
86
|
+
const id = itemIdFn(item);
|
|
87
|
+
itemContexts.set(id, {
|
|
88
|
+
item,
|
|
89
|
+
id,
|
|
90
|
+
selected: selectedContextsMap.has(id),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return itemContexts;
|
|
95
|
+
});
|
|
96
|
+
const visibleItems$ = computed(() => (open$() ? [...itemContexts$().values()] : []));
|
|
97
|
+
const highlighted$ = computed(() => {
|
|
98
|
+
const visibleItems = visibleItems$();
|
|
99
|
+
const highlightedIndex = highlightedIndex$();
|
|
100
|
+
return visibleItems.length && highlightedIndex != undefined ? visibleItems[highlightedIndex] : undefined;
|
|
101
|
+
});
|
|
102
|
+
const { directives: { floatingDirective, referenceDirective }, stores: { placement$ }, } = createFloatingUI({
|
|
103
|
+
props: {
|
|
104
|
+
computePositionOptions: asWritable(computed(() => ({
|
|
105
|
+
middleware: [
|
|
106
|
+
offset(5),
|
|
107
|
+
autoPlacement({
|
|
108
|
+
allowedPlacements: allowedPlacements$(),
|
|
109
|
+
}),
|
|
110
|
+
size(),
|
|
111
|
+
],
|
|
112
|
+
}))),
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
const widget = {
|
|
116
|
+
...stateStores({
|
|
117
|
+
visibleItems$,
|
|
118
|
+
highlighted$,
|
|
119
|
+
open$,
|
|
120
|
+
selectedContexts$,
|
|
121
|
+
filterText$,
|
|
122
|
+
placement$,
|
|
123
|
+
...stateProps,
|
|
124
|
+
}),
|
|
125
|
+
patch,
|
|
126
|
+
api: {
|
|
127
|
+
clear() {
|
|
128
|
+
selected$.set([]);
|
|
129
|
+
},
|
|
130
|
+
select(item) {
|
|
131
|
+
widget.api.toggleItem(item, true);
|
|
132
|
+
},
|
|
133
|
+
unselect(item) {
|
|
134
|
+
widget.api.toggleItem(item, false);
|
|
135
|
+
},
|
|
136
|
+
toggleItem(item, selected) {
|
|
137
|
+
const itemIdFn = itemIdFn$();
|
|
138
|
+
const itemId = itemIdFn(item);
|
|
139
|
+
const selectedContextsMap = selectedContextsMap$();
|
|
140
|
+
const isInSelected = selectedContextsMap.has(itemId);
|
|
141
|
+
if (selected == null) {
|
|
142
|
+
selected = !isInSelected;
|
|
143
|
+
}
|
|
144
|
+
if ((selected && !itemContexts$().has(itemId)) || (!selected && !isInSelected)) {
|
|
145
|
+
// Nothing to do in this case
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
selected$.update((selectedItems) => {
|
|
149
|
+
selectedItems = [...selectedItems]; // Mutate the array
|
|
150
|
+
if (selected && !isInSelected) {
|
|
151
|
+
selectedItems.push(item);
|
|
152
|
+
}
|
|
153
|
+
else if (!selected && isInSelected) {
|
|
154
|
+
const index = selectedItems.findIndex((item) => itemIdFn(item) === itemId);
|
|
155
|
+
selectedItems.splice(index, 1);
|
|
156
|
+
}
|
|
157
|
+
onSelectedChange$()?.(selectedItems);
|
|
158
|
+
return selectedItems;
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
clearText() {
|
|
162
|
+
// FIXME: not implemented yet!
|
|
163
|
+
},
|
|
164
|
+
highlight(item) {
|
|
165
|
+
const index = visibleItems$().findIndex((itemCtx) => itemCtx.item === item);
|
|
166
|
+
highlightedIndex$.set(index === -1 ? undefined : index);
|
|
167
|
+
},
|
|
168
|
+
highlightFirst() {
|
|
169
|
+
highlightedIndex$.set(0);
|
|
170
|
+
},
|
|
171
|
+
highlightPrevious() {
|
|
172
|
+
highlightedIndex$.update((highlightedIndex) => {
|
|
173
|
+
return highlightedIndex != null ? highlightedIndex - 1 : -1;
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
highlightNext() {
|
|
177
|
+
highlightedIndex$.update((highlightedIndex) => {
|
|
178
|
+
return highlightedIndex != null ? highlightedIndex + 1 : Infinity;
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
highlightLast() {
|
|
182
|
+
highlightedIndex$.set(-1);
|
|
183
|
+
},
|
|
184
|
+
focus(item) {
|
|
185
|
+
// FIXME: not implemented yet!
|
|
186
|
+
},
|
|
187
|
+
focusFirst() {
|
|
188
|
+
// FIXME: not implemented yet!
|
|
189
|
+
},
|
|
190
|
+
focusPrevious() {
|
|
191
|
+
// FIXME: not implemented yet!
|
|
192
|
+
},
|
|
193
|
+
focusNext() {
|
|
194
|
+
// FIXME: not implemented yet!
|
|
195
|
+
},
|
|
196
|
+
focusLast() {
|
|
197
|
+
// FIXME: not implemented yet!
|
|
198
|
+
},
|
|
199
|
+
open: () => widget.api.toggle(true),
|
|
200
|
+
close: () => widget.api.toggle(false),
|
|
201
|
+
toggle(isOpen) {
|
|
202
|
+
_dirtyOpen$.update((value) => (isOpen != null ? isOpen : !value));
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
directives: {
|
|
206
|
+
hasFocusDirective,
|
|
207
|
+
floatingDirective,
|
|
208
|
+
referenceDirective,
|
|
209
|
+
},
|
|
210
|
+
actions: {
|
|
211
|
+
onInput({ target }) {
|
|
212
|
+
const value = target.value;
|
|
213
|
+
patch({
|
|
214
|
+
open: value != null && value !== '',
|
|
215
|
+
filterText: value,
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
onInputKeydown(e) {
|
|
219
|
+
const { ctrlKey, key } = e;
|
|
220
|
+
let keyManaged = true;
|
|
221
|
+
switch (key) {
|
|
222
|
+
case 'ArrowDown': {
|
|
223
|
+
const isOpen = open$();
|
|
224
|
+
if (isOpen) {
|
|
225
|
+
if (ctrlKey) {
|
|
226
|
+
widget.api.highlightLast();
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
widget.api.highlightNext();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
widget.api.open();
|
|
234
|
+
widget.api.highlightFirst();
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
case 'ArrowUp':
|
|
239
|
+
if (ctrlKey) {
|
|
240
|
+
widget.api.highlightFirst();
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
widget.api.highlightPrevious();
|
|
244
|
+
}
|
|
245
|
+
break;
|
|
246
|
+
case 'Enter': {
|
|
247
|
+
const itemCtx = highlighted$();
|
|
248
|
+
if (itemCtx) {
|
|
249
|
+
widget.api.toggleItem(itemCtx.item);
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case 'Escape':
|
|
254
|
+
_dirtyOpen$.set(false);
|
|
255
|
+
break;
|
|
256
|
+
default:
|
|
257
|
+
keyManaged = false;
|
|
258
|
+
}
|
|
259
|
+
if (keyManaged) {
|
|
260
|
+
e.preventDefault();
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
return widget;
|
|
266
|
+
}
|