@astryxdesign/core 0.7.8-canary.ffd29c5 → 0.7.8
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/CHANGELOG.md +132 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts +1 -1
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +30 -14
- package/dist/BottomSheet/bottomSheetStack.js +1 -1
- package/dist/BottomSheet/useSheetGestures.d.ts +3 -1
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +4 -0
- package/dist/CheckboxList/CheckboxList.d.ts +13 -2
- package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxList.js +10 -4
- package/dist/CheckboxList/CheckboxListContext.d.ts +7 -0
- package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListContext.js +2 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +14 -2
- package/dist/CheckboxList/requiredGroup.d.ts +22 -0
- package/dist/CheckboxList/requiredGroup.d.ts.map +1 -0
- package/dist/CheckboxList/requiredGroup.js +52 -0
- package/dist/DateInput/PickerWheel.d.ts +1 -1
- package/dist/DateInput/PickerWheel.d.ts.map +1 -1
- package/dist/DateInput/PickerWheel.js +27 -6
- package/dist/DateInput/Wheel.d.ts +2 -2
- package/dist/DateInput/Wheel.d.ts.map +1 -1
- package/dist/DateInput/Wheel.js +20 -6
- package/dist/DateInput/datePickerTouch.d.ts +13 -0
- package/dist/DateInput/datePickerTouch.d.ts.map +1 -0
- package/dist/DateInput/datePickerTouch.js +14 -0
- package/dist/DateInput/wheelOptions.d.ts +22 -2
- package/dist/DateInput/wheelOptions.d.ts.map +1 -1
- package/dist/DateInput/wheelOptions.js +14 -2
- package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
- package/dist/DateTimeInput/TouchDateTimeField.js +62 -119
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +6 -1
- package/dist/Overlay/Overlay.d.ts.map +1 -1
- package/dist/Overlay/Overlay.js +7 -2
- package/dist/PowerSearch/PowerSearch.d.ts +4 -2
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchBottomSheet.d.ts +24 -1
- package/dist/PowerSearch/PowerSearchBottomSheet.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchBottomSheet.js +172 -50
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +15 -15
- package/dist/Resizable/createResizable.d.ts +70 -16
- package/dist/Resizable/createResizable.d.ts.map +1 -1
- package/dist/Resizable/createResizable.js +180 -22
- package/dist/Resizable/separatorAttributes.d.ts +45 -0
- package/dist/Resizable/separatorAttributes.d.ts.map +1 -0
- package/dist/Resizable/separatorAttributes.js +35 -0
- package/dist/Resizable/useResizable.js +2 -2
- package/dist/TimeInput/TimeWheels.d.ts +32 -0
- package/dist/TimeInput/TimeWheels.d.ts.map +1 -0
- package/dist/TimeInput/TimeWheels.js +144 -0
- package/dist/TimeInput/TouchTimeField.d.ts.map +1 -1
- package/dist/TimeInput/TouchTimeField.js +63 -118
- package/dist/TimeInput/timeWheelRules.d.ts +124 -0
- package/dist/TimeInput/timeWheelRules.d.ts.map +1 -0
- package/dist/TimeInput/timeWheelRules.js +277 -0
- package/dist/Tokenizer/TokenizerBottomSheet.js +8 -12
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +6 -0
- package/dist/astryx.css +3 -1
- package/dist/gestures/GestureHost.d.ts.map +1 -1
- package/dist/gestures/GestureHost.js +3 -0
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts.map +1 -1
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.js +5 -2
- package/dist/i18n/generated-english/Resizable/createResizable.generated.d.ts +8 -0
- package/dist/i18n/generated-english/Resizable/createResizable.generated.d.ts.map +1 -0
- package/dist/i18n/generated-english/Resizable/createResizable.generated.js +13 -0
- package/dist/i18n/generated-english/TimeInput/TimeWheels.generated.d.ts +8 -0
- package/dist/i18n/generated-english/TimeInput/TimeWheels.generated.d.ts.map +1 -0
- package/dist/i18n/generated-english/TimeInput/TimeWheels.generated.js +17 -0
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +5 -2
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +5 -2
- package/dist/keyboard/index.d.ts +17 -0
- package/dist/keyboard/index.d.ts.map +1 -0
- package/dist/keyboard/index.js +19 -0
- package/dist/keyboard/keyboardHeight.d.ts +149 -0
- package/dist/keyboard/keyboardHeight.d.ts.map +1 -0
- package/dist/keyboard/keyboardHeight.js +432 -0
- package/dist/keyboard/keyboardLayoutGuide.d.ts +59 -0
- package/dist/keyboard/keyboardLayoutGuide.d.ts.map +1 -0
- package/dist/keyboard/keyboardLayoutGuide.js +207 -0
- package/dist/keyboard/keyboardModel.d.ts +93 -0
- package/dist/keyboard/keyboardModel.d.ts.map +1 -0
- package/dist/keyboard/keyboardModel.js +220 -0
- package/dist/keyboard/rideKeyboard.d.ts +42 -0
- package/dist/keyboard/rideKeyboard.d.ts.map +1 -0
- package/dist/keyboard/rideKeyboard.js +77 -0
- package/dist/keyboard/useMobileKeyboard.d.ts +61 -0
- package/dist/keyboard/useMobileKeyboard.d.ts.map +1 -0
- package/dist/keyboard/useMobileKeyboard.js +699 -0
- package/dist/motion/android/index.d.ts +16 -0
- package/dist/motion/android/index.d.ts.map +1 -0
- package/dist/motion/android/index.js +18 -0
- package/dist/motion/android/keyboard.d.ts +83 -0
- package/dist/motion/android/keyboard.d.ts.map +1 -0
- package/dist/motion/android/keyboard.js +96 -0
- package/dist/motion/uikit/datePicker.d.ts +58 -0
- package/dist/motion/uikit/datePicker.d.ts.map +1 -0
- package/dist/motion/uikit/datePicker.js +60 -0
- package/dist/motion/uikit/index.d.ts +4 -3
- package/dist/motion/uikit/index.d.ts.map +1 -1
- package/dist/motion/uikit/index.js +4 -3
- package/dist/utils/interactionOverlay.stylex.d.ts +3 -2
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +3 -2
- package/dist/utils/layoutViewport.d.ts +25 -0
- package/dist/utils/layoutViewport.d.ts.map +1 -0
- package/dist/utils/layoutViewport.js +102 -0
- package/dist/utils/pressFeedback.d.ts.map +1 -1
- package/dist/utils/pressFeedback.js +74 -9
- package/dist/utils/pressGesture.d.ts +4 -2
- package/dist/utils/pressGesture.d.ts.map +1 -1
- package/dist/utils/pressGesture.js +3 -1
- package/dist/utils/selectionLock.d.ts +10 -1
- package/dist/utils/selectionLock.d.ts.map +1 -1
- package/dist/utils/selectionLock.js +31 -5
- package/dist/utils/sharedMutationObserver.d.ts +2 -1
- package/dist/utils/sharedMutationObserver.d.ts.map +1 -1
- package/dist/utils/sharedMutationObserver.js +2 -1
- package/dist/utils/timeParser.d.ts +2 -1
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +29 -3
- package/locales/en.json +21 -9
- package/locales/pseudo.json +15 -6
- package/package.json +5 -5
- package/src/BottomSheet/BottomSheet.doc.mjs +1 -1
- package/src/BottomSheet/BottomSheetPanel.tsx +41 -13
- package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +5 -0
- package/src/BottomSheet/bottomSheetStack.ts +1 -1
- package/src/BottomSheet/useSheetGestures.ts +6 -0
- package/src/CheckboxList/CheckboxList.doc.mjs +7 -0
- package/src/CheckboxList/CheckboxList.tsx +34 -2
- package/src/CheckboxList/CheckboxListContext.tsx +9 -1
- package/src/CheckboxList/CheckboxListItem.tsx +23 -2
- package/src/CheckboxList/requiredGroup.ts +77 -0
- package/src/DateInput/PickerWheel.tsx +37 -4
- package/src/DateInput/Wheel.tsx +30 -3
- package/src/DateInput/datePickerTouch.ts +15 -0
- package/src/DateInput/wheelOptions.ts +31 -2
- package/src/DateTimeInput/TouchDateTimeField.tsx +94 -178
- package/src/HoverCard/useHoverCard.tsx +6 -1
- package/src/Overlay/Overlay.tsx +7 -2
- package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
- package/src/PowerSearch/PowerSearch.tsx +4 -2
- package/src/PowerSearch/PowerSearchBottomSheet.tsx +237 -62
- package/src/Resizable/ResizeHandle.tsx +18 -26
- package/src/Resizable/createResizable.ts +242 -27
- package/src/Resizable/separatorAttributes.ts +73 -0
- package/src/Resizable/useResizable.ts +2 -2
- package/src/TimeInput/TimeWheels.tsx +218 -0
- package/src/TimeInput/TouchTimeField.tsx +93 -157
- package/src/TimeInput/timeWheelRules.ts +352 -0
- package/src/Tokenizer/TokenizerBottomSheet.tsx +8 -12
- package/src/Tooltip/useTooltip.tsx +6 -0
- package/src/gestures/GestureHost.ts +5 -4
- package/src/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.ts +5 -2
- package/src/i18n/generated-english/Resizable/createResizable.generated.ts +14 -0
- package/src/i18n/generated-english/TimeInput/TimeWheels.generated.ts +18 -0
- package/src/i18n/generated-locales/en.generated.ts +6 -4
- package/src/i18n/generated-locales/pseudo.generated.ts +6 -4
- package/src/keyboard/README.md +197 -0
- package/src/keyboard/index.ts +48 -0
- package/src/keyboard/keyboardHeight.ts +543 -0
- package/src/keyboard/keyboardLayoutGuide.ts +266 -0
- package/src/keyboard/keyboardModel.ts +301 -0
- package/src/keyboard/rideKeyboard.ts +100 -0
- package/src/keyboard/useMobileKeyboard.ts +866 -0
- package/src/motion/MEASUREMENTS.md +10 -0
- package/src/motion/README.md +2 -0
- package/src/motion/android/index.ts +18 -0
- package/src/motion/android/keyboard.ts +96 -0
- package/src/motion/uikit/datePicker.ts +60 -0
- package/src/motion/uikit/index.ts +4 -3
- package/src/utils/interactionOverlay.stylex.ts +3 -2
- package/src/utils/layoutViewport.ts +104 -0
- package/src/utils/pressFeedback.ts +99 -21
- package/src/utils/pressGesture.ts +4 -2
- package/src/utils/selectionLock.ts +40 -6
- package/src/utils/sharedMutationObserver.ts +2 -1
- package/src/utils/timeParser.ts +40 -3
- package/dist/BottomSheet/useMobileKeyboard.d.ts +0 -21
- package/dist/BottomSheet/useMobileKeyboard.d.ts.map +0 -1
- package/dist/BottomSheet/useMobileKeyboard.js +0 -413
- package/src/BottomSheet/useMobileKeyboard.ts +0 -542
|
@@ -181,7 +181,14 @@ export interface PowerSearchBottomSheetProps {
|
|
|
181
181
|
config: InternalConfig;
|
|
182
182
|
/** The caller's config, handed to Editor overrides as they expect it. */
|
|
183
183
|
configProp: PowerSearchConfig;
|
|
184
|
+
/** The committed filters; the draft the sheet edits starts from them. */
|
|
184
185
|
filters: ReadonlyArray<PowerSearchFilter>;
|
|
186
|
+
/**
|
|
187
|
+
* The field's `onChange`. Done replays the draft's edits through it, in
|
|
188
|
+
* order, onto the field's latest filters, each call carrying that list
|
|
189
|
+
* with the edit applied; nothing is reported before Done, and nothing when
|
|
190
|
+
* the field is read-only or disabled at Done.
|
|
191
|
+
*/
|
|
185
192
|
onChange: (
|
|
186
193
|
filters: ReadonlyArray<PowerSearchFilter>,
|
|
187
194
|
changeType: PowerSearchChangeType,
|
|
@@ -208,6 +215,116 @@ export interface PowerSearchBottomSheetProps {
|
|
|
208
215
|
// Styles
|
|
209
216
|
// =============================================================================
|
|
210
217
|
|
|
218
|
+
/** One edit of the draft: the draft before and after it, and its type and index in the draft; Done replays it onto the field's latest filters. */
|
|
219
|
+
interface DraftEdit {
|
|
220
|
+
before: ReadonlyArray<PowerSearchFilter>;
|
|
221
|
+
after: ReadonlyArray<PowerSearchFilter>;
|
|
222
|
+
changeType: PowerSearchChangeType;
|
|
223
|
+
index: number;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Whether two filter lists hold the same filters in the same order, by the
|
|
227
|
+
// key-order-insensitive signature the sheet matches filters with.
|
|
228
|
+
function hasSameFilters(
|
|
229
|
+
a: ReadonlyArray<PowerSearchFilter>,
|
|
230
|
+
b: ReadonlyArray<PowerSearchFilter>,
|
|
231
|
+
): boolean {
|
|
232
|
+
return (
|
|
233
|
+
a.length === b.length &&
|
|
234
|
+
a.every(
|
|
235
|
+
(filter, index) =>
|
|
236
|
+
b[index] != null &&
|
|
237
|
+
filterSignature(filter) === filterSignature(b[index]),
|
|
238
|
+
)
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// The filters `before` holds that `after` does not, as a multiset by
|
|
243
|
+
// signature, in `before` order.
|
|
244
|
+
function removedFilters(
|
|
245
|
+
before: ReadonlyArray<PowerSearchFilter>,
|
|
246
|
+
after: ReadonlyArray<PowerSearchFilter>,
|
|
247
|
+
): PowerSearchFilter[] {
|
|
248
|
+
const remaining = new Map<string, number>();
|
|
249
|
+
for (const filter of after) {
|
|
250
|
+
const signature = filterSignature(filter);
|
|
251
|
+
remaining.set(signature, (remaining.get(signature) ?? 0) + 1);
|
|
252
|
+
}
|
|
253
|
+
return before.filter(filter => {
|
|
254
|
+
const signature = filterSignature(filter);
|
|
255
|
+
const count = remaining.get(signature) ?? 0;
|
|
256
|
+
if (count > 0) {
|
|
257
|
+
remaining.set(signature, count - 1);
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
return true;
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Replays the draft's edits onto `base`, the field's latest filters, and
|
|
266
|
+
* reports each through `report` with the list after it. An add appends; an
|
|
267
|
+
* edit replaces the first filter matching the one it edited; a removal drops
|
|
268
|
+
* the filters it removed, by signature. An edit or removal whose target is
|
|
269
|
+
* not in the base list is skipped: the parent removed it meanwhile.
|
|
270
|
+
*/
|
|
271
|
+
export function replayDraftEdits(
|
|
272
|
+
base: ReadonlyArray<PowerSearchFilter>,
|
|
273
|
+
edits: ReadonlyArray<DraftEdit>,
|
|
274
|
+
report: (
|
|
275
|
+
filters: ReadonlyArray<PowerSearchFilter>,
|
|
276
|
+
changeType: PowerSearchChangeType,
|
|
277
|
+
index: number,
|
|
278
|
+
) => void,
|
|
279
|
+
): ReadonlyArray<PowerSearchFilter> {
|
|
280
|
+
let current = base;
|
|
281
|
+
for (const edit of edits) {
|
|
282
|
+
if (edit.changeType === 'add') {
|
|
283
|
+
const added = edit.after[edit.index];
|
|
284
|
+
if (added == null) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
current = [...current, added];
|
|
288
|
+
report(current, 'add', current.length - 1);
|
|
289
|
+
} else if (edit.changeType === 'edit') {
|
|
290
|
+
const original = edit.before[edit.index];
|
|
291
|
+
const updated = edit.after[edit.index];
|
|
292
|
+
if (original == null || updated == null) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const signature = filterSignature(original);
|
|
296
|
+
const index = current.findIndex(
|
|
297
|
+
filter => filterSignature(filter) === signature,
|
|
298
|
+
);
|
|
299
|
+
if (index === -1) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
current = current.map((filter, i) => (i === index ? updated : filter));
|
|
303
|
+
report(current, 'edit', index);
|
|
304
|
+
} else {
|
|
305
|
+
const removed = removedFilters(edit.before, edit.after).map(
|
|
306
|
+
filterSignature,
|
|
307
|
+
);
|
|
308
|
+
const indexes: number[] = [];
|
|
309
|
+
for (const signature of removed) {
|
|
310
|
+
const index = current.findIndex(
|
|
311
|
+
(filter, i) =>
|
|
312
|
+
!indexes.includes(i) && filterSignature(filter) === signature,
|
|
313
|
+
);
|
|
314
|
+
if (index !== -1) {
|
|
315
|
+
indexes.push(index);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (indexes.length === 0) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
current = current.filter((_, i) => !indexes.includes(i));
|
|
322
|
+
report(current, 'remove', Math.min(...indexes));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return current;
|
|
326
|
+
}
|
|
327
|
+
|
|
211
328
|
// The trailing plus a field row shows when choosing it adds the filter at
|
|
212
329
|
// once, and the Add filter actions' glyph. Core's default icon set has no plus.
|
|
213
330
|
function PlusIcon(props: SVGProps<SVGSVGElement>) {
|
|
@@ -834,8 +951,8 @@ export function PowerSearchBottomSheet({
|
|
|
834
951
|
resultCountText,
|
|
835
952
|
config,
|
|
836
953
|
configProp,
|
|
837
|
-
filters,
|
|
838
|
-
onChange,
|
|
954
|
+
filters: committedFilters,
|
|
955
|
+
onChange: onCommit,
|
|
839
956
|
searchSource,
|
|
840
957
|
placeholder,
|
|
841
958
|
isReadOnly,
|
|
@@ -854,6 +971,35 @@ export function PowerSearchBottomSheet({
|
|
|
854
971
|
const formatList = useListFormatter();
|
|
855
972
|
const isInteractive = !isDisabled && !isReadOnly;
|
|
856
973
|
|
|
974
|
+
// ---- draft ----
|
|
975
|
+
// One open flow edits one draft of the filter list; `onChange` waits for
|
|
976
|
+
// Done, and a dismissal with a changed draft asks first.
|
|
977
|
+
const [filtersAtOpen, setFiltersAtOpen] = useState(() => committedFilters);
|
|
978
|
+
const [filters, setFilters] = useState(() => committedFilters);
|
|
979
|
+
// The draft as of the last edit, for the edit log's `before` lists.
|
|
980
|
+
const draftRef = useRef(filters);
|
|
981
|
+
const draftEditsRef = useRef<DraftEdit[]>([]);
|
|
982
|
+
const onChange = useCallback(
|
|
983
|
+
(
|
|
984
|
+
next: ReadonlyArray<PowerSearchFilter>,
|
|
985
|
+
changeType: PowerSearchChangeType,
|
|
986
|
+
index: number,
|
|
987
|
+
) => {
|
|
988
|
+
draftEditsRef.current.push({
|
|
989
|
+
before: draftRef.current,
|
|
990
|
+
after: next,
|
|
991
|
+
changeType,
|
|
992
|
+
index,
|
|
993
|
+
});
|
|
994
|
+
draftRef.current = next;
|
|
995
|
+
setFilters(next);
|
|
996
|
+
},
|
|
997
|
+
[],
|
|
998
|
+
);
|
|
999
|
+
const isDraftDirty = !hasSameFilters(filtersAtOpen, filters);
|
|
1000
|
+
const [isDiscardConfirmationOpen, setIsDiscardConfirmationOpen] =
|
|
1001
|
+
useState(false);
|
|
1002
|
+
|
|
857
1003
|
const contentSearchFieldKey = config.config.contentSearchFieldKey;
|
|
858
1004
|
const contentSearchOperator =
|
|
859
1005
|
contentSearchFieldKey == null
|
|
@@ -961,9 +1107,6 @@ export function PowerSearchBottomSheet({
|
|
|
961
1107
|
const [contentQuery, setContentQuery] = useState('');
|
|
962
1108
|
const [isClearConfirmationOpen, setIsClearConfirmationOpen] = useState(false);
|
|
963
1109
|
const clearAllButtonRef = useRef<HTMLButtonElement>(null);
|
|
964
|
-
// Set while Clear all commits, so the result count that changes in that
|
|
965
|
-
// commit is spoken with Clear all's outcome, as one message.
|
|
966
|
-
const isClearingRef = useRef(false);
|
|
967
1110
|
|
|
968
1111
|
// `useAnnounce` delivers inside the open Astryx modal that encloses its
|
|
969
1112
|
// caller, and this component renders above the
|
|
@@ -990,13 +1133,21 @@ export function PowerSearchBottomSheet({
|
|
|
990
1133
|
if (isOpen !== wasOpen) {
|
|
991
1134
|
setWasOpen(isOpen);
|
|
992
1135
|
if (isOpen) {
|
|
1136
|
+
// A fresh draft from the committed filters; the entry step is judged
|
|
1137
|
+
// against them, not the previous session's draft.
|
|
1138
|
+
setFiltersAtOpen(committedFilters);
|
|
1139
|
+
setFilters(committedFilters);
|
|
1140
|
+
draftRef.current = committedFilters;
|
|
1141
|
+
draftEditsRef.current = [];
|
|
1142
|
+
const startsOnAddFilter = committedFilters.length === 0 && isInteractive;
|
|
993
1143
|
setFieldQuery('');
|
|
994
1144
|
setContentQuery('');
|
|
995
|
-
setStep(
|
|
996
|
-
setFieldPickerOrigin(
|
|
1145
|
+
setStep(startsOnAddFilter ? 'fields' : 'manage');
|
|
1146
|
+
setFieldPickerOrigin(startsOnAddFilter ? 'trigger' : 'manage');
|
|
997
1147
|
} else {
|
|
998
1148
|
setStep(null);
|
|
999
1149
|
setIsClearConfirmationOpen(false);
|
|
1150
|
+
setIsDiscardConfirmationOpen(false);
|
|
1000
1151
|
}
|
|
1001
1152
|
}
|
|
1002
1153
|
|
|
@@ -1005,6 +1156,34 @@ export function PowerSearchBottomSheet({
|
|
|
1005
1156
|
setStep(null);
|
|
1006
1157
|
onOpenChange(false);
|
|
1007
1158
|
}, [onOpenChange]);
|
|
1159
|
+
// Done replays each edit in order onto the field's latest filters, so a
|
|
1160
|
+
// consumer gets the calls a popover session makes and keeps a filter it
|
|
1161
|
+
// added meanwhile.
|
|
1162
|
+
const commitAndClose = useCallback(() => {
|
|
1163
|
+
// A field that became read-only while the flow was open reports nothing.
|
|
1164
|
+
if (isDraftDirty && isInteractive) {
|
|
1165
|
+
replayDraftEdits(committedFilters, draftEditsRef.current, onCommit);
|
|
1166
|
+
}
|
|
1167
|
+
draftEditsRef.current = [];
|
|
1168
|
+
closeFlow();
|
|
1169
|
+
}, [closeFlow, committedFilters, isDraftDirty, isInteractive, onCommit]);
|
|
1170
|
+
const requestDismiss = useCallback(() => {
|
|
1171
|
+
if (isDraftDirty) {
|
|
1172
|
+
setIsDiscardConfirmationOpen(true);
|
|
1173
|
+
} else {
|
|
1174
|
+
closeFlow();
|
|
1175
|
+
}
|
|
1176
|
+
}, [closeFlow, isDraftDirty]);
|
|
1177
|
+
const closeDiscardConfirmation = () => {
|
|
1178
|
+
// eslint-disable-next-line @eslint-react/dom-no-flush-sync -- the confirmation must be gone before focus moves back into the sheet
|
|
1179
|
+
flushSync(() => setIsDiscardConfirmationOpen(false));
|
|
1180
|
+
const target =
|
|
1181
|
+
stepRef.current === 'manage'
|
|
1182
|
+
? (managerAddButtonRef.current ??
|
|
1183
|
+
manageSheetRef.current?.closest<HTMLElement>('[tabindex="-1"]'))
|
|
1184
|
+
: fieldsSheetRef.current?.closest<HTMLElement>('[tabindex="-1"]');
|
|
1185
|
+
target?.focus({preventScroll: true});
|
|
1186
|
+
};
|
|
1008
1187
|
|
|
1009
1188
|
// Whether a Filters row is a button: the field interactive and the
|
|
1010
1189
|
// filter not read-only. The row opens the value step — the editor for its
|
|
@@ -1279,17 +1458,8 @@ export function PowerSearchBottomSheet({
|
|
|
1279
1458
|
// is quiet then, so the change is spoken here, inside the dialog. Only a
|
|
1280
1459
|
// change after the flow opened is spoken.
|
|
1281
1460
|
const announcedResultCountRef = useRef(resultCountText);
|
|
1282
|
-
// The latest result count, for Clear all's handler to read after its
|
|
1283
|
-
// commit has flushed.
|
|
1284
|
-
const latestResultCountRef = useRef(resultCountText);
|
|
1285
|
-
useLayoutEffect(() => {
|
|
1286
|
-
latestResultCountRef.current = resultCountText;
|
|
1287
|
-
}, [resultCountText]);
|
|
1288
1461
|
useEffect(() => {
|
|
1289
|
-
if (
|
|
1290
|
-
isClearingRef.current ||
|
|
1291
|
-
resultCountText === announcedResultCountRef.current
|
|
1292
|
-
) {
|
|
1462
|
+
if (resultCountText === announcedResultCountRef.current) {
|
|
1293
1463
|
return;
|
|
1294
1464
|
}
|
|
1295
1465
|
announcedResultCountRef.current = resultCountText;
|
|
@@ -1630,52 +1800,31 @@ export function PowerSearchBottomSheet({
|
|
|
1630
1800
|
// confirmation must be gone before focus moves into the sheet and the
|
|
1631
1801
|
// outcome is routed into the sheet's region, so its close is flushed
|
|
1632
1802
|
// first. The outcome is "Editable filters removed" while a read-only filter
|
|
1633
|
-
// remains, otherwise "All filters removed"
|
|
1634
|
-
//
|
|
1635
|
-
// sheet's first remaining control: a kept read-only row has none,
|
|
1636
|
-
// filter.
|
|
1803
|
+
// remains, otherwise "All filters removed"; the result count follows the
|
|
1804
|
+
// applied filters, which change on Done, so none changes here. Focus moves
|
|
1805
|
+
// to the sheet's first remaining control: a kept read-only row has none,
|
|
1806
|
+
// so Add filter.
|
|
1637
1807
|
const closeClearConfirmation = (confirmed: boolean) => {
|
|
1638
1808
|
const kept = filters.filter(filter => filter.isReadOnly);
|
|
1639
1809
|
const firstRemovedIndex = filters.findIndex(filter => !filter.isReadOnly);
|
|
1640
1810
|
const isClearing = confirmed && isInteractive && firstRemovedIndex >= 0;
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
}
|
|
1649
|
-
});
|
|
1650
|
-
} finally {
|
|
1651
|
-
isClearingRef.current = false;
|
|
1652
|
-
}
|
|
1811
|
+
// eslint-disable-next-line @eslint-react/dom-no-flush-sync -- the confirmation must be gone before focus moves into the sheet and the words are routed into it
|
|
1812
|
+
flushSync(() => {
|
|
1813
|
+
setIsClearConfirmationOpen(false);
|
|
1814
|
+
if (isClearing) {
|
|
1815
|
+
onChange(kept, 'remove', firstRemovedIndex);
|
|
1816
|
+
}
|
|
1817
|
+
});
|
|
1653
1818
|
if (!isClearing) {
|
|
1654
1819
|
clearAllButtonRef.current?.focus();
|
|
1655
1820
|
return;
|
|
1656
1821
|
}
|
|
1657
1822
|
managerAddButtonRef.current?.focus({preventScroll: true});
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
announce(
|
|
1664
|
-
changedCount == null
|
|
1665
|
-
? t('@astryx.powersearch.editableFiltersRemoved')
|
|
1666
|
-
: t('@astryx.powersearch.sheet.editableFiltersClearedWithCount', {
|
|
1667
|
-
resultCount: changedCount,
|
|
1668
|
-
}),
|
|
1669
|
-
);
|
|
1670
|
-
} else {
|
|
1671
|
-
announce(
|
|
1672
|
-
changedCount == null
|
|
1673
|
-
? t('@astryx.powersearch.allFiltersRemoved')
|
|
1674
|
-
: t('@astryx.powersearch.sheet.filtersClearedWithCount', {
|
|
1675
|
-
resultCount: changedCount,
|
|
1676
|
-
}),
|
|
1677
|
-
);
|
|
1678
|
-
}
|
|
1823
|
+
announce(
|
|
1824
|
+
kept.length > 0
|
|
1825
|
+
? t('@astryx.powersearch.editableFiltersRemoved')
|
|
1826
|
+
: t('@astryx.powersearch.allFiltersRemoved'),
|
|
1827
|
+
);
|
|
1679
1828
|
};
|
|
1680
1829
|
|
|
1681
1830
|
const handleOperatorSelect = useCallback((operator: PowerSearchOperator) => {
|
|
@@ -1776,10 +1925,10 @@ export function PowerSearchBottomSheet({
|
|
|
1776
1925
|
const handleManageOpenChange = useCallback(
|
|
1777
1926
|
(isSheetOpen: boolean) => {
|
|
1778
1927
|
if (!isSheetOpen && stepRef.current === 'manage') {
|
|
1779
|
-
|
|
1928
|
+
requestDismiss();
|
|
1780
1929
|
}
|
|
1781
1930
|
},
|
|
1782
|
-
[
|
|
1931
|
+
[requestDismiss],
|
|
1783
1932
|
);
|
|
1784
1933
|
const handleFieldsOpenChange = useCallback(
|
|
1785
1934
|
(isSheetOpen: boolean) => {
|
|
@@ -1787,13 +1936,13 @@ export function PowerSearchBottomSheet({
|
|
|
1787
1936
|
return;
|
|
1788
1937
|
}
|
|
1789
1938
|
if (fieldPickerOrigin === 'trigger') {
|
|
1790
|
-
|
|
1939
|
+
requestDismiss();
|
|
1791
1940
|
return;
|
|
1792
1941
|
}
|
|
1793
1942
|
queueManagerAddFocus();
|
|
1794
1943
|
setStep('manage');
|
|
1795
1944
|
},
|
|
1796
|
-
[
|
|
1945
|
+
[fieldPickerOrigin, queueManagerAddFocus, requestDismiss],
|
|
1797
1946
|
);
|
|
1798
1947
|
const handleValueOpenChange = useCallback(
|
|
1799
1948
|
(isSheetOpen: boolean) => {
|
|
@@ -2076,7 +2225,11 @@ export function PowerSearchBottomSheet({
|
|
|
2076
2225
|
// is spoken in this sheet when it appears.
|
|
2077
2226
|
isManageSheetOpen && (
|
|
2078
2227
|
<EmptyState
|
|
2079
|
-
title={t(
|
|
2228
|
+
title={t(
|
|
2229
|
+
isDraftDirty
|
|
2230
|
+
? '@astryx.powersearch.sheet.noFiltersInDraft'
|
|
2231
|
+
: '@astryx.powersearch.sheet.noFilters',
|
|
2232
|
+
)}
|
|
2080
2233
|
description={t(
|
|
2081
2234
|
'@astryx.powersearch.sheet.noFiltersDescription',
|
|
2082
2235
|
)}
|
|
@@ -2157,7 +2310,10 @@ export function PowerSearchBottomSheet({
|
|
|
2157
2310
|
)}
|
|
2158
2311
|
</div>
|
|
2159
2312
|
</div>
|
|
2160
|
-
{
|
|
2313
|
+
{/* The footer stays while the draft differs from the applied
|
|
2314
|
+
filters: an emptied draft still needs Done to apply the
|
|
2315
|
+
removals. */}
|
|
2316
|
+
{(filterRows.length > 0 || isDraftDirty) && (
|
|
2161
2317
|
<div {...stylex.props(styles.footer, styles.fixedFooter)}>
|
|
2162
2318
|
{isClearAllShown && (
|
|
2163
2319
|
<Button
|
|
@@ -2178,7 +2334,7 @@ export function PowerSearchBottomSheet({
|
|
|
2178
2334
|
? styles.footerEqualAction
|
|
2179
2335
|
: [styles.footerSoleAction, styles.touchHitArea]
|
|
2180
2336
|
}
|
|
2181
|
-
onClick={
|
|
2337
|
+
onClick={commitAndClose}
|
|
2182
2338
|
width={isClearAllShown ? undefined : '100%'}
|
|
2183
2339
|
/>
|
|
2184
2340
|
</div>
|
|
@@ -2373,7 +2529,7 @@ export function PowerSearchBottomSheet({
|
|
|
2373
2529
|
variant="primary"
|
|
2374
2530
|
width="100%"
|
|
2375
2531
|
xstyle={styles.touchAction}
|
|
2376
|
-
onClick={
|
|
2532
|
+
onClick={commitAndClose}
|
|
2377
2533
|
/>
|
|
2378
2534
|
</div>
|
|
2379
2535
|
)}
|
|
@@ -2518,6 +2674,25 @@ export function PowerSearchBottomSheet({
|
|
|
2518
2674
|
onAction={() => closeClearConfirmation(true)}
|
|
2519
2675
|
/>
|
|
2520
2676
|
)}
|
|
2677
|
+
{isDiscardConfirmationOpen && (
|
|
2678
|
+
<SheetConfirmation
|
|
2679
|
+
isOpen
|
|
2680
|
+
onOpenChange={isConfirmationOpen => {
|
|
2681
|
+
if (!isConfirmationOpen) {
|
|
2682
|
+
closeDiscardConfirmation();
|
|
2683
|
+
}
|
|
2684
|
+
}}
|
|
2685
|
+
title={t('@astryx.powersearch.sheet.discardTitle')}
|
|
2686
|
+
description={t('@astryx.powersearch.sheet.discardDescription')}
|
|
2687
|
+
actionLabel={t('@astryx.powersearch.sheet.discardConfirm')}
|
|
2688
|
+
cancelLabel={t('@astryx.powersearch.sheet.keepEditing')}
|
|
2689
|
+
onAction={() => {
|
|
2690
|
+
setIsDiscardConfirmationOpen(false);
|
|
2691
|
+
draftEditsRef.current = [];
|
|
2692
|
+
closeFlow();
|
|
2693
|
+
}}
|
|
2694
|
+
/>
|
|
2695
|
+
)}
|
|
2521
2696
|
</>
|
|
2522
2697
|
);
|
|
2523
2698
|
}
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
type ResizeGesture,
|
|
36
36
|
type ResizeGestureModel,
|
|
37
37
|
} from './resizeGesture';
|
|
38
|
+
import {separatorAttributes} from './separatorAttributes';
|
|
38
39
|
import {themeProps} from '../utils/themeProps';
|
|
39
40
|
|
|
40
41
|
import {useDevWarning} from '../hooks/useDevWarning';
|
|
@@ -529,19 +530,8 @@ export function ResizeHandle({
|
|
|
529
530
|
}, [hasRegion, regionGesture]);
|
|
530
531
|
|
|
531
532
|
// --- ARIA ---
|
|
532
|
-
// When collapsed the panel's real size (0) sits below aria-valuemin, which
|
|
533
|
-
// is invalid per WCAG 4.1.2. Clamp aria-valuenow to the minimum and announce
|
|
534
|
-
// the true state via aria-valuetext instead; the valuetext is removed as
|
|
535
|
-
// soon as the panel expands so the numeric value reads again.
|
|
536
533
|
const sizeNow = resizable?._size;
|
|
537
534
|
const minNow = resizable?._minSizePx;
|
|
538
|
-
const ariaValueNow =
|
|
539
|
-
sizeNow == null || minNow == null
|
|
540
|
-
? undefined
|
|
541
|
-
: isCollapsed
|
|
542
|
-
? Math.max(sizeNow, minNow)
|
|
543
|
-
: sizeNow;
|
|
544
|
-
const ariaValueMin = minNow;
|
|
545
535
|
// A separator with a value needs a maximum (WAI-ARIA, APG window splitter).
|
|
546
536
|
// Unbounded regions report the basis they can fill; a hand-built props
|
|
547
537
|
// object without it reports only a configured maximum.
|
|
@@ -549,14 +539,22 @@ export function ResizeHandle({
|
|
|
549
539
|
? (getEffectiveMaxPx(resizable) ??
|
|
550
540
|
(resizable._maxSizePx !== Infinity ? resizable._maxSizePx : undefined))
|
|
551
541
|
: undefined;
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
542
|
+
// The attributes createResizable writes on a plain element, from the same
|
|
543
|
+
// derivation.
|
|
544
|
+
const aria = separatorAttributes({
|
|
545
|
+
direction,
|
|
546
|
+
isDisabled,
|
|
547
|
+
value:
|
|
548
|
+
sizeNow == null || minNow == null
|
|
549
|
+
? undefined
|
|
550
|
+
: {
|
|
551
|
+
size: sizeNow,
|
|
552
|
+
minPx: minNow,
|
|
553
|
+
maxPx: effectiveMax,
|
|
554
|
+
isCollapsed,
|
|
555
|
+
collapsedText: t('@astryx.resizable.collapsed'),
|
|
556
|
+
},
|
|
557
|
+
});
|
|
560
558
|
|
|
561
559
|
// The Grip pill takes the pointer as the Grab zone does, so a pill drawn
|
|
562
560
|
// outside the zone still resizes.
|
|
@@ -591,14 +589,8 @@ export function ResizeHandle({
|
|
|
591
589
|
<div
|
|
592
590
|
ref={useMergedRefs(ref, handleRef)}
|
|
593
591
|
role="separator"
|
|
594
|
-
aria
|
|
595
|
-
aria-valuenow={ariaValueNow}
|
|
596
|
-
aria-valuemin={ariaValueMin}
|
|
597
|
-
aria-valuemax={ariaValueMax}
|
|
598
|
-
aria-valuetext={ariaValueText}
|
|
592
|
+
{...aria}
|
|
599
593
|
aria-label={label}
|
|
600
|
-
aria-disabled={isDisabled || undefined}
|
|
601
|
-
tabIndex={isDisabled ? -1 : 0}
|
|
602
594
|
onDoubleClick={() => {
|
|
603
595
|
if (!isDisabled) {
|
|
604
596
|
gesture?.doubleClick();
|