@jsenv/navi 0.0.1 → 0.1.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.
Files changed (138) hide show
  1. package/dist/jsenv_navi.js +22954 -0
  2. package/index.js +66 -16
  3. package/package.json +22 -11
  4. package/src/actions.js +50 -26
  5. package/src/browser_integration/browser_integration.js +31 -6
  6. package/src/browser_integration/via_history.js +42 -9
  7. package/src/components/action_execution/render_actionable_component.jsx +6 -4
  8. package/src/components/action_execution/use_action.js +51 -282
  9. package/src/components/action_execution/use_execute_action.js +106 -92
  10. package/src/components/action_execution/use_run_on_mount.js +9 -0
  11. package/src/components/action_renderer.jsx +21 -32
  12. package/src/components/demos/0_button_demo.html +574 -103
  13. package/src/components/demos/10_column_reordering_debug.html +277 -0
  14. package/src/components/demos/11_table_selection_debug.html +432 -0
  15. package/src/components/demos/1_checkbox_demo.html +579 -202
  16. package/src/components/demos/2_input_textual_demo.html +81 -138
  17. package/src/components/demos/3_radio_demo.html +0 -2
  18. package/src/components/demos/4_select_demo.html +19 -23
  19. package/src/components/demos/6_tablist_demo.html +77 -0
  20. package/src/components/demos/7_table_selection_demo.html +176 -0
  21. package/src/components/demos/8_table_fixed_headers_demo.html +584 -0
  22. package/src/components/demos/9_table_column_drag_demo.html +325 -0
  23. package/src/components/demos/action/0_button_demo.html +2 -4
  24. package/src/components/demos/action/1_input_text_demo.html +643 -222
  25. package/src/components/demos/action/3_details_demo.html +146 -115
  26. package/src/components/demos/action/4_input_checkbox_demo.html +442 -322
  27. package/src/components/demos/action/5_input_checkbox_state_demo.html +270 -0
  28. package/src/components/demos/action/6_checkbox_list_demo.html +304 -72
  29. package/src/components/demos/action/7_radio_list_demo.html +310 -170
  30. package/src/components/demos/action/{8_editable_text_demo.html → 8_editable_demo.html} +65 -76
  31. package/src/components/demos/action/9_link_demo.html +84 -62
  32. package/src/components/demos/ui_transition/0_action_renderer_ui_transition_demo.html +695 -0
  33. package/src/components/demos/ui_transition/1_nested_ui_transition_demo.html +429 -0
  34. package/src/components/demos/ui_transition/2_height_transition_test.html +295 -0
  35. package/src/components/details/details.jsx +62 -64
  36. package/src/components/edition/editable.jsx +186 -0
  37. package/src/components/field/README.md +247 -0
  38. package/src/components/{input → field}/button.jsx +151 -130
  39. package/src/components/field/checkbox_list.jsx +184 -0
  40. package/src/components/{collect_form_element_values.js → field/collect_form_element_values.js} +7 -4
  41. package/src/components/{input → field}/field_css.js +4 -1
  42. package/src/components/field/form.jsx +211 -0
  43. package/src/components/{input → field}/input.jsx +1 -0
  44. package/src/components/{input → field}/input_checkbox.jsx +132 -155
  45. package/src/components/{input → field}/input_radio.jsx +135 -46
  46. package/src/components/{input → field}/input_textual.jsx +247 -173
  47. package/src/components/field/label.jsx +32 -0
  48. package/src/components/field/radio_list.jsx +182 -0
  49. package/src/components/{input → field}/select.jsx +17 -32
  50. package/src/components/field/use_action_events.js +132 -0
  51. package/src/components/field/use_form_events.js +55 -0
  52. package/src/components/field/use_ui_state_controller.js +506 -0
  53. package/src/components/item_tracker/README.md +461 -0
  54. package/src/components/item_tracker/use_isolated_item_tracker.jsx +209 -0
  55. package/src/components/item_tracker/use_isolated_item_tracker_demo.html +148 -0
  56. package/src/components/item_tracker/use_isolated_item_tracker_demo.jsx +460 -0
  57. package/src/components/item_tracker/use_item_tracker.jsx +143 -0
  58. package/src/components/item_tracker/use_item_tracker_demo.html +207 -0
  59. package/src/components/item_tracker/use_item_tracker_demo.jsx +216 -0
  60. package/src/components/keyboard_shortcuts/active_keyboard_shortcuts.jsx +87 -0
  61. package/src/components/keyboard_shortcuts/aria_key_shortcuts.js +61 -0
  62. package/src/components/keyboard_shortcuts/keyboard_key_meta.js +17 -0
  63. package/src/components/keyboard_shortcuts/keyboard_shortcuts.js +371 -0
  64. package/src/components/link/link.jsx +65 -102
  65. package/src/components/link/link_with_icon.jsx +52 -0
  66. package/src/components/loader/loader_background.jsx +85 -64
  67. package/src/components/loader/rectangle_loading.jsx +38 -19
  68. package/src/components/route.jsx +8 -4
  69. package/src/components/selection/selection.jsx +1583 -0
  70. package/src/components/svg/font_sized_svg.jsx +45 -0
  71. package/src/components/svg/icon_and_text.jsx +21 -0
  72. package/src/components/svg/svg_mask_overlay.jsx +105 -0
  73. package/src/components/table/drag/table_drag.jsx +506 -0
  74. package/src/components/table/resize/table_resize.jsx +650 -0
  75. package/src/components/table/resize/table_size.js +43 -0
  76. package/src/components/table/selection/table_selection.js +106 -0
  77. package/src/components/table/selection/table_selection.jsx +203 -0
  78. package/src/components/table/sticky/sticky_group.js +354 -0
  79. package/src/components/table/sticky/table_sticky.js +25 -0
  80. package/src/components/table/sticky/table_sticky.jsx +501 -0
  81. package/src/components/table/table.jsx +721 -0
  82. package/src/components/table/table_css.js +211 -0
  83. package/src/components/table/table_ui.jsx +49 -0
  84. package/src/components/table/use_cells_and_columns.js +90 -0
  85. package/src/components/table/use_object_array_to_cells.js +46 -0
  86. package/src/components/table/z_indexes.js +23 -0
  87. package/src/components/tablist/tablist.jsx +99 -0
  88. package/src/components/text/overflow.jsx +15 -0
  89. package/src/components/text/text_and_count.jsx +28 -0
  90. package/src/components/ui_transition.jsx +128 -0
  91. package/src/components/use_auto_focus.js +58 -7
  92. package/src/components/use_batch_during_render.js +33 -0
  93. package/src/components/use_debounce_true.js +7 -7
  94. package/src/components/use_dependencies_diff.js +35 -0
  95. package/src/components/use_focus_group.js +4 -3
  96. package/src/components/use_initial_value.js +8 -34
  97. package/src/components/use_signal_sync.js +1 -1
  98. package/src/components/use_stable_callback.js +68 -0
  99. package/src/components/use_state_array.js +16 -9
  100. package/src/docs/actions.md +22 -0
  101. package/src/notes.md +33 -12
  102. package/src/route/route.js +97 -47
  103. package/src/store/resource_graph.js +2 -1
  104. package/src/store/tests/{resource_graph_dependencies.test.js → resource_graph_dependencies.test_manual.js} +13 -13
  105. package/src/utils/is_signal.js +20 -0
  106. package/src/utils/stringify_for_display.js +4 -23
  107. package/src/validation/constraints/confirm_constraint.js +14 -0
  108. package/src/validation/constraints/create_unique_value_constraint.js +27 -0
  109. package/src/validation/constraints/native_constraints.js +313 -0
  110. package/src/validation/constraints/readonly_constraint.js +36 -0
  111. package/src/validation/constraints/single_space_constraint.js +13 -0
  112. package/src/validation/custom_constraint_validation.js +599 -0
  113. package/src/validation/custom_message.js +18 -0
  114. package/src/validation/demos/browser_style.png +0 -0
  115. package/src/validation/demos/form_validation_demo.html +142 -0
  116. package/src/validation/demos/form_validation_demo_preact.html +87 -0
  117. package/src/validation/demos/form_validation_native_popover_demo.html +168 -0
  118. package/src/validation/demos/form_validation_vs_native_demo.html +172 -0
  119. package/src/validation/demos/validation_message_demo.html +203 -0
  120. package/src/validation/hooks/use_constraints.js +23 -0
  121. package/src/validation/hooks/use_custom_validation_ref.js +73 -0
  122. package/src/validation/hooks/use_validation_message.js +19 -0
  123. package/src/validation/validation_message.js +741 -0
  124. package/src/components/editable_text/editable_text.jsx +0 -96
  125. package/src/components/form.jsx +0 -144
  126. package/src/components/input/checkbox_list.jsx +0 -294
  127. package/src/components/input/field.jsx +0 -61
  128. package/src/components/input/radio_list.jsx +0 -283
  129. package/src/components/input/use_form_event.js +0 -20
  130. package/src/components/input/use_on_change.js +0 -12
  131. package/src/components/selection/selection.js +0 -5
  132. package/src/components/selection/selection_context.jsx +0 -262
  133. package/src/components/shortcut/shortcut_context.jsx +0 -390
  134. package/src/components/use_action_events.js +0 -37
  135. package/src/utils/iterable_weak_set.js +0 -62
  136. /package/src/components/demos/action/{11_nested_shortcuts_demo.html → 11_nested_shortcuts_demo.xhtml} +0 -0
  137. /package/src/components/{shortcut → keyboard_shortcuts}/os.js +0 -0
  138. /package/src/route/{route.test.html → route.xtest.html} +0 -0
@@ -1,283 +0,0 @@
1
- import { requestAction } from "@jsenv/validation";
2
- import { forwardRef } from "preact/compat";
3
- import { useEffect, useImperativeHandle, useRef, useState } from "preact/hooks";
4
- import { useNavState } from "../../browser_integration/browser_integration.js";
5
- import { useActionStatus } from "../../use_action_status.js";
6
- import { renderActionableComponent } from "../action_execution/render_actionable_component.jsx";
7
- import {
8
- useActionBoundToOneParam,
9
- useOneFormParam,
10
- } from "../action_execution/use_action.js";
11
- import { useExecuteAction } from "../action_execution/use_execute_action.js";
12
- import { useActionEvents } from "../use_action_events.js";
13
- import { useRefArray } from "../use_ref_array.js";
14
- import { Field } from "./field.jsx";
15
- import { InputRadio } from "./input_radio.jsx";
16
- import { useFormEvents } from "./use_form_event.js";
17
-
18
- import.meta.css = /* css */ `
19
- .radio_list {
20
- display: flex;
21
- flex-direction: column;
22
- }
23
- `;
24
-
25
- export const RadioList = forwardRef((props, ref) => {
26
- return renderActionableComponent(props, ref, {
27
- Basic: RadioListBasic,
28
- WithAction: RadioListWithAction,
29
- InsideForm: RadioListInsideForm,
30
- });
31
- });
32
-
33
- const RadioListControlled = forwardRef((props, ref) => {
34
- const {
35
- name,
36
- value,
37
- label,
38
- loading,
39
- disabled,
40
- readOnly,
41
- children,
42
- onChange,
43
- required,
44
- ...rest
45
- } = props;
46
-
47
- const innerRef = useRef();
48
- useImperativeHandle(ref, () => innerRef.current);
49
-
50
- return (
51
- <fieldset className="radio_list" ref={innerRef} {...rest}>
52
- {label ? <legend>{label}</legend> : null}
53
- {children.map((child) => {
54
- const {
55
- label,
56
- readOnly: childReadOnly,
57
- disabled: childDisabled,
58
- loading: childLoading,
59
- onChange: childOnChange,
60
- value: childValue,
61
- ...childRest
62
- } = child;
63
-
64
- const radio = (
65
- <InputRadio
66
- {...childRest}
67
- // ignoreForm: each input is controller by this list
68
- // we don't want the input to try to update the form because it's already done here
69
- ignoreForm
70
- name={name}
71
- value={childValue}
72
- checked={childValue === value}
73
- readOnly={readOnly || childReadOnly}
74
- disabled={disabled || childDisabled}
75
- loading={loading || childLoading}
76
- required={required}
77
- onChange={(event) => {
78
- onChange(event);
79
- childOnChange?.(event);
80
- }}
81
- />
82
- );
83
-
84
- return <Field key={childValue} input={radio} label={label} />;
85
- })}
86
- </fieldset>
87
- );
88
- });
89
-
90
- const RadioListBasic = forwardRef((props, ref) => {
91
- const { value: initialValue, id, children, ...rest } = props;
92
-
93
- const innerRef = useRef();
94
- useImperativeHandle(ref, () => innerRef.current);
95
-
96
- const [navState, setNavState] = useNavState(id);
97
- const valueAtStart = navState === undefined ? initialValue : navState;
98
- const [value, setValue] = useState(valueAtStart);
99
- useEffect(() => {
100
- setNavState(value);
101
- }, [value]);
102
-
103
- return (
104
- <RadioListControlled
105
- ref={innerRef}
106
- value={value}
107
- onChange={(event) => {
108
- const radio = event.target;
109
- const radioIsChecked = radio.checked;
110
- if (!radioIsChecked) {
111
- return;
112
- }
113
- const value = radio.value;
114
- setValue(value);
115
- }}
116
- {...rest}
117
- >
118
- {children}
119
- </RadioListControlled>
120
- );
121
- });
122
-
123
- const RadioListWithAction = forwardRef((props, ref) => {
124
- const {
125
- id,
126
- name,
127
- value: externalValue,
128
- valueSignal,
129
- action,
130
- children,
131
- onCancel,
132
- onActionPrevented,
133
- onActionStart,
134
- onActionAbort,
135
- onActionError,
136
- onActionEnd,
137
- actionErrorEffect,
138
- ...rest
139
- } = props;
140
-
141
- const innerRef = useRef();
142
- useImperativeHandle(ref, () => innerRef.current);
143
-
144
- const [navState, setNavState, resetNavState] = useNavState(id);
145
- const [boundAction, value, setValue, resetValue] = useActionBoundToOneParam(
146
- action,
147
- name,
148
- valueSignal ? valueSignal : externalValue,
149
- navState,
150
- );
151
- const { loading: actionLoading } = useActionStatus(boundAction);
152
- const executeAction = useExecuteAction(innerRef, {
153
- errorEffect: actionErrorEffect,
154
- });
155
- const actionRequesterRef = useRef(null);
156
- useEffect(() => {
157
- setNavState(value);
158
- }, [value]);
159
-
160
- useActionEvents(innerRef, {
161
- onCancel: (e, reason) => {
162
- resetNavState();
163
- resetValue();
164
- onCancel?.(e, reason);
165
- },
166
- onPrevented: onActionPrevented,
167
- onAction: (actionEvent) => {
168
- actionRequesterRef.current = actionEvent.detail.requester;
169
- executeAction(actionEvent);
170
- },
171
- onStart: onActionStart,
172
- onAbort: (e) => {
173
- resetValue();
174
- onActionAbort?.(e);
175
- },
176
- onError: (error) => {
177
- resetValue();
178
- onActionError?.(error);
179
- },
180
- onEnd: (e) => {
181
- resetNavState();
182
- onActionEnd?.(e);
183
- },
184
- });
185
-
186
- const childRefArray = useRefArray(children, (child) => child.value);
187
-
188
- return (
189
- <RadioListControlled
190
- ref={innerRef}
191
- name={name}
192
- value={value}
193
- data-action={boundAction}
194
- onChange={(event) => {
195
- const radio = event.target;
196
- const radioIsChecked = radio.checked;
197
- if (!radioIsChecked) {
198
- return;
199
- }
200
- const value = radio.value;
201
- setValue(value);
202
- const radioListContainer = innerRef.current;
203
- requestAction(boundAction, {
204
- event,
205
- target: radioListContainer,
206
- requester: radio,
207
- });
208
- }}
209
- {...rest}
210
- >
211
- {children.map((child, i) => {
212
- const childRef = childRefArray[i];
213
- return {
214
- ...child,
215
- ref: childRef,
216
- loading:
217
- child.loading ||
218
- (actionLoading && actionRequesterRef.current === childRef.current),
219
- readOnly: child.readOnly || actionLoading,
220
- };
221
- })}
222
- </RadioListControlled>
223
- );
224
- });
225
-
226
- const RadioListInsideForm = forwardRef((props, ref) => {
227
- const {
228
- formContext,
229
- id,
230
- name,
231
- readOnly,
232
- value: externalValue,
233
- children,
234
- ...rest
235
- } = props;
236
- const { formIsReadOnly } = formContext;
237
-
238
- const innerRef = useRef();
239
- useImperativeHandle(ref, () => innerRef.current);
240
-
241
- const [navState, setNavState] = useNavState(id);
242
- const [value, setValue, resetValue] = useOneFormParam(
243
- name,
244
- externalValue,
245
- navState,
246
- );
247
- useEffect(() => {
248
- setNavState(value);
249
- }, [value]);
250
-
251
- useFormEvents(innerRef, {
252
- onFormReset: () => {
253
- setValue(undefined);
254
- },
255
- onFormActionAbort: () => {
256
- resetValue();
257
- },
258
- onFormActionError: () => {
259
- resetValue();
260
- },
261
- });
262
-
263
- return (
264
- <RadioListControlled
265
- ref={innerRef}
266
- name={name}
267
- value={value}
268
- readOnly={readOnly || formIsReadOnly}
269
- onChange={(event) => {
270
- const radio = event.target;
271
- const radioIsChecked = radio.checked;
272
- if (!radioIsChecked) {
273
- return;
274
- }
275
- const value = radio.value;
276
- setValue(value);
277
- }}
278
- {...rest}
279
- >
280
- {children}
281
- </RadioListControlled>
282
- );
283
- });
@@ -1,20 +0,0 @@
1
- import { useLayoutEffect } from "preact/hooks";
2
- import { addManyEventListeners } from "../../utils/add_many_event_listeners.js";
3
-
4
- export const useFormEvents = (
5
- elementRef,
6
- { onFormReset, onFormActionAbort, onFormActionError },
7
- ) => {
8
- useLayoutEffect(() => {
9
- const element = elementRef.current;
10
- const form = element.form;
11
-
12
- return addManyEventListeners(form, {
13
- reset: onFormReset,
14
- actionabort: onFormActionAbort,
15
- actionerror: (e) => {
16
- onFormActionError?.(e.detail.error);
17
- },
18
- });
19
- }, [onFormReset, onFormActionAbort, onFormActionError]);
20
- };
@@ -1,12 +0,0 @@
1
- import { useLayoutEffect } from "preact/hooks";
2
-
3
- export const useOnChange = (innerRef, callback) => {
4
- // we must use a custom event listener because preact bind onChange to onInput for compat with react
5
- useLayoutEffect(() => {
6
- const input = innerRef.current;
7
- input.addEventListener("change", callback);
8
- return () => {
9
- input.removeEventListener("change", callback);
10
- };
11
- }, [callback]);
12
- };
@@ -1,5 +0,0 @@
1
- export {
2
- SelectionProvider,
3
- useRegisterSelectionValue,
4
- useSelectionContext,
5
- } from "./selection_context.jsx";
@@ -1,262 +0,0 @@
1
- import { canInterceptKeys } from "@jsenv/dom";
2
- import { createContext } from "preact";
3
- import { useContext, useLayoutEffect, useRef } from "preact/hooks";
4
-
5
- const SelectionContext = createContext(null);
6
-
7
- export const SelectionProvider = ({ value = [], onChange, children }) => {
8
- const selection = value || [];
9
- const registryRef = useRef([]); // Array<value>
10
- const anchorRef = useRef(null);
11
-
12
- const contextValue = {
13
- selection,
14
-
15
- register: (value) => {
16
- const registry = registryRef.current;
17
- const existingIndex = registry.indexOf(value);
18
- if (existingIndex >= 0) {
19
- console.warn(
20
- `SelectionContext: Attempted to register an already registered value: ${value}. All values must be unique.`,
21
- );
22
- return;
23
- }
24
- registry.push(value);
25
- },
26
- unregister: (value) => {
27
- const registry = registryRef.current;
28
- const index = registry.indexOf(value);
29
- if (index >= 0) {
30
- registry.splice(index, 1);
31
- }
32
- },
33
- setAnchor: (value) => {
34
- anchorRef.current = value;
35
- },
36
- isSelected: (itemValue) => {
37
- return selection.includes(itemValue);
38
- },
39
- getAllItems: () => {
40
- return registryRef.current;
41
- },
42
- getRange: (fromValue, toValue) => {
43
- const registry = registryRef.current;
44
-
45
- // Find indices of fromValue and toValue
46
- let fromIndex = -1;
47
- let toIndex = -1;
48
- let index = 0;
49
- for (const valueCandidate of registry) {
50
- if (valueCandidate === fromValue) {
51
- fromIndex = index;
52
- }
53
- if (valueCandidate === toValue) {
54
- toIndex = index;
55
- }
56
- index++;
57
- }
58
-
59
- if (fromIndex >= 0 && toIndex >= 0) {
60
- // Select all items between fromIndex and toIndex (inclusive)
61
- const start = Math.min(fromIndex, toIndex);
62
- const end = Math.max(fromIndex, toIndex);
63
- const valueInRangeArray = registry.slice(start, end + 1);
64
- return valueInRangeArray;
65
- }
66
- return [];
67
- },
68
-
69
- // basic methods to manipulate selection
70
- set: (newSelection, event = null) => {
71
- if (
72
- newSelection.length === selection.length &&
73
- newSelection.every((value, index) => value === selection[index])
74
- ) {
75
- return;
76
- }
77
- onChange?.(newSelection, event);
78
- },
79
- add: (arrayOfValueToAddToSelection, event = null) => {
80
- const selectionWithValues = [];
81
- for (const value of selection) {
82
- selectionWithValues.push(value);
83
- }
84
- let modified = false;
85
- for (const valueToAdd of arrayOfValueToAddToSelection) {
86
- if (selectionWithValues.includes(valueToAdd)) {
87
- continue;
88
- }
89
- modified = true;
90
- selectionWithValues.push(valueToAdd);
91
- }
92
- if (modified) {
93
- onChange?.(selectionWithValues, event);
94
- }
95
- },
96
- remove: (arrayOfValueToRemoveFromSelection, event = null) => {
97
- let modified = false;
98
- const selectionWithoutValues = [];
99
- for (const value of selection) {
100
- if (arrayOfValueToRemoveFromSelection.includes(value)) {
101
- modified = true;
102
- // If we're removing the last selected value, clear it
103
- if (value === anchorRef.current) {
104
- anchorRef.current = null;
105
- }
106
- } else {
107
- selectionWithoutValues.push(value);
108
- }
109
- }
110
-
111
- if (modified) {
112
- onChange?.(selectionWithoutValues, event);
113
- }
114
- },
115
-
116
- // Convenience method for multi-select: toggle, addFromLastSelectedTo
117
- toggle: (value, event = null) => {
118
- if (selection.includes(value)) {
119
- contextValue.remove([value], event);
120
- } else {
121
- contextValue.add([value], event);
122
- }
123
- },
124
- // Convenience method for shift-click: add range from last selected to target value
125
- setFromAnchorTo: (value, event = null) => {
126
- const anchorValue = anchorRef.current;
127
-
128
- // Make sure the last selected value is still in the current selection
129
- if (anchorValue && selection.includes(anchorValue)) {
130
- const range = contextValue.getRange(anchorValue, value);
131
- contextValue.set(range, event);
132
- } else {
133
- // No valid previous selection, just select this one
134
- contextValue.set([value], event);
135
- }
136
- },
137
-
138
- getValueAfter: (value) => {
139
- const registry = registryRef.current;
140
- const index = registry.indexOf(value);
141
- if (index < 0 || index >= registry.length - 1) {
142
- return null; // No next value
143
- }
144
- return registry[index + 1];
145
- },
146
- getValueBefore: (value) => {
147
- const registry = registryRef.current;
148
- const index = registry.indexOf(value);
149
- if (index <= 0) {
150
- return null; // No previous value
151
- }
152
- return registry[index - 1];
153
- },
154
- };
155
-
156
- return (
157
- <SelectionContext.Provider value={contextValue}>
158
- {children}
159
- </SelectionContext.Provider>
160
- );
161
- };
162
-
163
- export const useSelectionContext = () => {
164
- return useContext(SelectionContext);
165
- };
166
-
167
- export const useRegisterSelectionValue = (value) => {
168
- const selectionContext = useSelectionContext();
169
-
170
- useLayoutEffect(() => {
171
- if (selectionContext) {
172
- selectionContext.register(value);
173
- return () => selectionContext.unregister(value);
174
- }
175
- return undefined;
176
- }, [selectionContext, value]);
177
- };
178
-
179
- export const clickToSelect = (clickEvent, { selectionContext, value }) => {
180
- if (clickEvent.defaultPrevented) {
181
- // If the click was prevented by another handler, do not interfere
182
- return;
183
- }
184
-
185
- const isMultiSelect = clickEvent.metaKey || clickEvent.ctrlKey;
186
- const isShiftSelect = clickEvent.shiftKey;
187
- const isSingleSelect = !isMultiSelect && !isShiftSelect;
188
-
189
- if (isSingleSelect) {
190
- // Single select - replace entire selection with just this item
191
- selectionContext.set([value], clickEvent);
192
- return;
193
- }
194
- if (isMultiSelect) {
195
- // here no need to prevent nav on <a> but it means cmd + click will both multi select
196
- // and open in a new tab
197
- selectionContext.toggle(value, clickEvent);
198
- return;
199
- }
200
- if (isShiftSelect) {
201
- clickEvent.preventDefault(); // Prevent navigation
202
- selectionContext.setFromAnchorTo(value, clickEvent);
203
- return;
204
- }
205
- };
206
-
207
- export const keydownToSelect = (keydownEvent, { selectionContext, value }) => {
208
- if (!canInterceptKeys(keydownEvent)) {
209
- return;
210
- }
211
-
212
- if (keydownEvent.key === "Shift") {
213
- selectionContext.setAnchor(value);
214
- return;
215
- }
216
-
217
- const isMultiSelect = keydownEvent.metaKey || keydownEvent.ctrlKey;
218
- const isShiftSelect = keydownEvent.shiftKey;
219
- const { key } = keydownEvent;
220
- if (key === "a") {
221
- if (!isMultiSelect) {
222
- return;
223
- }
224
- keydownEvent.preventDefault(); // prevent default select all text behavior
225
- selectionContext.set(selectionContext.getAllItems(), keydownEvent);
226
- return;
227
- }
228
- if (key === "ArrowDown") {
229
- const nextValue = selectionContext.getValueAfter(value);
230
- if (!nextValue) {
231
- return; // No next value to select
232
- }
233
- keydownEvent.preventDefault(); // Prevent default scrolling behavior
234
- if (isShiftSelect) {
235
- selectionContext.setFromAnchorTo(nextValue, keydownEvent);
236
- return;
237
- }
238
- if (isMultiSelect) {
239
- selectionContext.add([nextValue], keydownEvent);
240
- return;
241
- }
242
- selectionContext.set([nextValue], keydownEvent);
243
- return;
244
- }
245
- if (key === "ArrowUp") {
246
- const previousValue = selectionContext.getValueBefore(value);
247
- if (!previousValue) {
248
- return; // No previous value to select
249
- }
250
- keydownEvent.preventDefault(); // Prevent default scrolling behavior
251
- if (isShiftSelect) {
252
- selectionContext.setFromAnchorTo(previousValue, keydownEvent);
253
- return;
254
- }
255
- if (isMultiSelect) {
256
- selectionContext.add([previousValue], keydownEvent);
257
- return;
258
- }
259
- selectionContext.set([previousValue], keydownEvent);
260
- return;
261
- }
262
- };