@12-apps/ui 6.24.7 → 6.25.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/components-catalog.md +1 -1
- package/dist/_internal/components/data-display/CardKit/CardKebab.js +4 -4
- package/dist/_internal/components/data-display/CardKit/use-remove-confirm.js +4 -4
- package/dist/_internal/components/data-display/CardKit/use-row-confirm.js +4 -4
- package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +34 -34
- package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +40 -40
- package/dist/_internal/components/data-display/DataViews/DeleteViewDialog.js +4 -4
- package/dist/_internal/components/data-display/DataViews/ManageViewsDialog.js +5 -5
- package/dist/_internal/components/data-display/DataViews/SaveViewModal.js +4 -4
- package/dist/_internal/components/data-display/DataViews/data-views-category-pill.js +15 -15
- package/dist/_internal/components/data-display/DataViews/data-views-columns.js +5 -5
- package/dist/_internal/components/data-display/DataViews/data-views-display-panel.js +5 -5
- package/dist/_internal/components/data-display/DataViews/data-views-empty.js +4 -4
- package/dist/_internal/components/data-display/DataViews/data-views-export.js +4 -4
- package/dist/_internal/components/data-display/DataViews/data-views-filter-panel.js +16 -16
- package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +6 -6
- package/dist/_internal/components/data-display/DataViews/data-views-grid-helpers.js +4 -4
- package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +31 -31
- package/dist/_internal/components/data-display/DataViews/data-views-inline-bar.js +20 -20
- package/dist/_internal/components/data-display/DataViews/data-views-more-filters.js +4 -4
- package/dist/_internal/components/data-display/DataViews/data-views-select-all-strip.js +4 -4
- package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +9 -9
- package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +7 -7
- package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +8 -8
- package/dist/_internal/components/data-display/DataViews/data-views-view-nav.js +4 -4
- package/dist/_internal/components/data-display/DataViews/use-data-views-state.js +6 -6
- package/dist/_internal/components/feedback/ConfirmAction/ConfirmButton.js +4 -4
- package/dist/_internal/components/feedback/index.js +4 -4
- package/dist/_internal/components/form/Button/Button.js +3 -3
- package/dist/_internal/components/form/Button/Button.metrics.js +3 -1
- package/dist/_internal/components/form/Button/Button.styles.js +2 -2
- package/dist/_internal/components/form/CategorySelect/CategoryPanel.js +8 -8
- package/dist/_internal/components/form/CategorySelect/CategoryPanelFoot.js +2 -2
- package/dist/_internal/components/form/CategorySelect/CategoryPanelHead.js +2 -2
- package/dist/_internal/components/form/CategorySelect/CategoryPanelList.js +4 -4
- package/dist/_internal/components/form/CategorySelect/CategoryQuickActions.js +2 -2
- package/dist/_internal/components/form/CategorySelect/CategoryRows.js +7 -9
- package/dist/_internal/components/form/CategorySelect/CategorySelect.js +14 -14
- package/dist/_internal/components/form/CategorySelect/CategorySelect.styles.js +3 -1
- package/dist/_internal/components/form/CategorySelect/CategoryTrigger.js +2 -2
- package/dist/_internal/components/form/CategorySelect/category-keyboard.js +2 -2
- package/dist/_internal/components/form/CategorySelect/category-tree.js +11 -3
- package/dist/_internal/components/form/CategorySelect/useCategoryCommit.js +2 -2
- package/dist/_internal/components/form/CategorySelect/useCategoryKeyHandler.js +3 -3
- package/dist/_internal/components/form/CategorySelect/useCategorySelect.js +2 -2
- package/dist/_internal/components/form/index.js +3 -3
- package/dist/_internal/components/form/total-form/Fields/CategoryField.js +15 -15
- package/dist/_internal/components/form/total-form/Fields/index.js +16 -16
- package/dist/_internal/components/form/total-form/SubmitButton.js +4 -4
- package/dist/_internal/components/index.js +5 -5
- package/dist/_internal/components/navigation/AppHeader/AppHeader.details.js +4 -4
- package/dist/_internal/components/navigation/index.js +4 -4
- package/dist/_internal/social-login-button/social-login-button.js +4 -4
- package/dist/_internal/user-avatar/user-avatar.js +4 -4
- package/dist/{chunk-6JGPPTLX.js → chunk-2GI7ANEL.js} +3 -3
- package/dist/{chunk-U2ZDN2AL.js → chunk-2PBGVMM2.js} +2 -2
- package/dist/{chunk-ITGG5OKP.js → chunk-5LQ7PKMW.js} +3 -1
- package/dist/chunk-5LQ7PKMW.js.map +1 -0
- package/dist/{chunk-4T7TXRGM.js → chunk-6ZY5XU4J.js} +3 -3
- package/dist/{chunk-CHPZTF34.js → chunk-7KWBGMQC.js} +2 -2
- package/dist/{chunk-6NSIZ2ZO.js → chunk-7MN6I5NK.js} +3 -3
- package/dist/{chunk-PN5CQKA2.js → chunk-AXTLFK4T.js} +2 -2
- package/dist/{chunk-JT6TERQL.js → chunk-BB2OUERT.js} +2 -2
- package/dist/{chunk-XUJG2HFW.js → chunk-BH5BXLGI.js} +2 -2
- package/dist/{chunk-4TXIZ5WI.js → chunk-CJUOBTLL.js} +2 -2
- package/dist/{chunk-NWNSUHEB.js → chunk-CT5K725B.js} +2 -2
- package/dist/{chunk-6OORHAE7.js → chunk-EQ7PWGBN.js} +3 -3
- package/dist/{chunk-EJQW2IYW.js → chunk-FPFUXRD2.js} +2 -2
- package/dist/{chunk-ZRPGVHTR.js → chunk-GGI7GM5O.js} +2 -2
- package/dist/{chunk-KIHQE5YA.js → chunk-GVDJQMFI.js} +2 -2
- package/dist/{chunk-TX5V42AY.js → chunk-HEBQS7QP.js} +3 -3
- package/dist/{chunk-BUQ2JUND.js → chunk-JO6BBSGY.js} +2 -2
- package/dist/{chunk-4ZV7N5CU.js → chunk-JXPHU3K5.js} +4 -4
- package/dist/{chunk-TW3BXVEU.js → chunk-KMXAPYU2.js} +2 -2
- package/dist/{chunk-CJBBGMIA.js → chunk-MNXVXW4T.js} +2 -2
- package/dist/{chunk-OFOYMZRY.js → chunk-NPN7JTRM.js} +22 -47
- package/dist/chunk-NPN7JTRM.js.map +1 -0
- package/dist/{chunk-BPLFM2GL.js → chunk-NYEA7JAI.js} +3 -3
- package/dist/{chunk-O6AZDQJJ.js → chunk-OE2ZVL67.js} +2 -2
- package/dist/{chunk-4523RYE7.js → chunk-OG2FP6SE.js} +2 -2
- package/dist/{chunk-XKV5ZIBG.js → chunk-OGGPQVIG.js} +2 -2
- package/dist/{chunk-SQNW22ZZ.js → chunk-OV4WREMW.js} +3 -3
- package/dist/{chunk-OVLNF4AZ.js → chunk-P7IVAMAU.js} +24 -35
- package/dist/chunk-P7IVAMAU.js.map +1 -0
- package/dist/{chunk-6LGWJFQK.js → chunk-PFDM2OIP.js} +2 -2
- package/dist/chunk-PN4HVM43.js +160 -0
- package/dist/chunk-PN4HVM43.js.map +1 -0
- package/dist/{chunk-WWM2YTW4.js → chunk-R2AZOWEZ.js} +21 -27
- package/dist/chunk-R2AZOWEZ.js.map +1 -0
- package/dist/{chunk-2GB5CIN6.js → chunk-RZVUY2MO.js} +8 -4
- package/dist/chunk-RZVUY2MO.js.map +1 -0
- package/dist/{chunk-YTOYMJPM.js → chunk-SLVQ33AN.js} +15 -5
- package/dist/chunk-SLVQ33AN.js.map +1 -0
- package/dist/{chunk-H5VEJDEK.js → chunk-TVPPA4RD.js} +7 -7
- package/dist/{chunk-ISH42UC7.js → chunk-USJPZ5Q7.js} +2 -2
- package/dist/{chunk-5OIH36KX.js → chunk-V4FKHLEB.js} +2 -2
- package/dist/{chunk-ZUIJ3N7N.js → chunk-V6Z4XS3I.js} +5 -3
- package/dist/chunk-V6Z4XS3I.js.map +1 -0
- package/dist/{chunk-JM64OEU6.js → chunk-WAPJ2DL3.js} +10 -13
- package/dist/chunk-WAPJ2DL3.js.map +1 -0
- package/dist/{chunk-QPLWDSYG.js → chunk-WM6Y2S6W.js} +4 -4
- package/dist/{chunk-EMMPNB6D.js → chunk-X4Z62ZQY.js} +2 -2
- package/dist/{chunk-ZAB65STX.js → chunk-XHHR6G35.js} +2 -2
- package/dist/{chunk-OP65D4UR.js → chunk-XR6WBW23.js} +2 -2
- package/dist/{chunk-WIT3Y7JT.js → chunk-Y2B3I3IH.js} +2 -2
- package/dist/{chunk-R6SNYC3D.js → chunk-Y3KRUT7O.js} +2 -2
- package/dist/{chunk-TPLKJHNN.js → chunk-ZC7RQDZ5.js} +8 -7
- package/dist/chunk-ZC7RQDZ5.js.map +1 -0
- package/dist/{chunk-TFO6GBW7.js → chunk-ZS44PY7T.js} +5 -1
- package/dist/chunk-ZS44PY7T.js.map +1 -0
- package/dist/{chunk-AJ6XEO4E.js → chunk-ZUY65MFH.js} +2 -2
- package/dist/{chunk-4X4KM4ZB.js → chunk-ZZMZF7PZ.js} +2 -2
- package/dist/data-display/CardKit.js +5 -5
- package/dist/data-display/DataViews.js +40 -40
- package/dist/feedback/ConfirmAction.js +4 -4
- package/dist/form/Button.js +3 -3
- package/dist/form/CategorySelect.js +24 -16
- package/dist/form/total-form.js +20 -20
- package/dist/native/chunks/{chunk-ZAQ3AZJM.js → chunk-2R2R35C5.js} +2 -2
- package/dist/native/chunks/{chunk-ATS7STZJ.js → chunk-WL5LXVCK.js} +1 -1
- package/dist/native/chunks/{chunk-ATS7STZJ.js.map → chunk-WL5LXVCK.js.map} +1 -1
- package/dist/native/data-display/EmptyState.js +2 -2
- package/dist/native/data-display/ErrorState.js +1 -1
- package/dist/native/data-display/FleetMap.js +2 -2
- package/dist/native/form/Button.js +1 -1
- package/dist/navigation/AppHeader.js +4 -4
- package/dist/social-login-button.js +4 -4
- package/dist/types/components/form/Button/Button.d.ts +7 -3
- package/dist/types/components/form/Button/Button.d.ts.map +1 -1
- package/dist/types/components/form/Button/Button.metrics.d.ts +25 -0
- package/dist/types/components/form/Button/Button.metrics.d.ts.map +1 -1
- package/dist/types/components/form/Button/Button.types.d.ts +37 -4
- package/dist/types/components/form/Button/Button.types.d.ts.map +1 -1
- package/dist/types/components/form/CategorySelect/CategoryPanel.d.ts.map +1 -1
- package/dist/types/components/form/CategorySelect/CategoryPanelList.d.ts +6 -0
- package/dist/types/components/form/CategorySelect/CategoryPanelList.d.ts.map +1 -1
- package/dist/types/components/form/CategorySelect/CategoryQuickActions.d.ts.map +1 -1
- package/dist/types/components/form/CategorySelect/CategoryRows.d.ts +12 -24
- package/dist/types/components/form/CategorySelect/CategoryRows.d.ts.map +1 -1
- package/dist/types/components/form/CategorySelect/CategorySelect.d.ts +10 -7
- package/dist/types/components/form/CategorySelect/CategorySelect.d.ts.map +1 -1
- package/dist/types/components/form/CategorySelect/CategorySelect.styles.d.ts +11 -0
- package/dist/types/components/form/CategorySelect/CategorySelect.styles.d.ts.map +1 -1
- package/dist/types/components/form/CategorySelect/CategorySelect.types.d.ts +10 -2
- package/dist/types/components/form/CategorySelect/CategorySelect.types.d.ts.map +1 -1
- package/dist/types/components/form/CategorySelect/category-keyboard.d.ts.map +1 -1
- package/dist/types/components/form/CategorySelect/category-tree.d.ts +52 -20
- package/dist/types/components/form/CategorySelect/category-tree.d.ts.map +1 -1
- package/dist/types/components/form/CategorySelect/index.d.ts +1 -1
- package/dist/types/components/form/CategorySelect/index.d.ts.map +1 -1
- package/dist/types/components/form/CategorySelect/useCategorySelect.d.ts +2 -0
- package/dist/types/components/form/CategorySelect/useCategorySelect.d.ts.map +1 -1
- package/dist/types-native/components/form/Button/Button.metrics.d.ts +25 -0
- package/dist/types-native/components/form/Button/Button.metrics.d.ts.map +1 -1
- package/dist/user-avatar.js +4 -4
- package/package.json +1 -1
- package/src/components/form/Button/Button.md +39 -0
- package/src/components/form/Button/Button.metrics.ts +26 -0
- package/src/components/form/Button/Button.tsx +77 -4
- package/src/components/form/Button/Button.types.ts +47 -5
- package/src/components/form/CategorySelect/CategoryPanel.tsx +1 -0
- package/src/components/form/CategorySelect/CategoryPanelList.tsx +42 -34
- package/src/components/form/CategorySelect/CategoryQuickActions.tsx +6 -4
- package/src/components/form/CategorySelect/CategoryRows.tsx +45 -81
- package/src/components/form/CategorySelect/CategorySelect.md +47 -13
- package/src/components/form/CategorySelect/CategorySelect.styles.ts +13 -0
- package/src/components/form/CategorySelect/CategorySelect.tsx +16 -14
- package/src/components/form/CategorySelect/CategorySelect.types.ts +10 -2
- package/src/components/form/CategorySelect/category-keyboard.ts +27 -31
- package/src/components/form/CategorySelect/category-tree.ts +143 -63
- package/src/components/form/CategorySelect/index.ts +4 -0
- package/src/components/form/CategorySelect/useCategorySelect.ts +15 -1
- package/dist/chunk-2GB5CIN6.js.map +0 -1
- package/dist/chunk-ITGG5OKP.js.map +0 -1
- package/dist/chunk-JM64OEU6.js.map +0 -1
- package/dist/chunk-LR6MSKTT.js +0 -124
- package/dist/chunk-LR6MSKTT.js.map +0 -1
- package/dist/chunk-OFOYMZRY.js.map +0 -1
- package/dist/chunk-OVLNF4AZ.js.map +0 -1
- package/dist/chunk-TFO6GBW7.js.map +0 -1
- package/dist/chunk-TPLKJHNN.js.map +0 -1
- package/dist/chunk-WWM2YTW4.js.map +0 -1
- package/dist/chunk-YTOYMJPM.js.map +0 -1
- package/dist/chunk-ZUIJ3N7N.js.map +0 -1
- /package/dist/{chunk-6JGPPTLX.js.map → chunk-2GI7ANEL.js.map} +0 -0
- /package/dist/{chunk-U2ZDN2AL.js.map → chunk-2PBGVMM2.js.map} +0 -0
- /package/dist/{chunk-4T7TXRGM.js.map → chunk-6ZY5XU4J.js.map} +0 -0
- /package/dist/{chunk-CHPZTF34.js.map → chunk-7KWBGMQC.js.map} +0 -0
- /package/dist/{chunk-6NSIZ2ZO.js.map → chunk-7MN6I5NK.js.map} +0 -0
- /package/dist/{chunk-PN5CQKA2.js.map → chunk-AXTLFK4T.js.map} +0 -0
- /package/dist/{chunk-JT6TERQL.js.map → chunk-BB2OUERT.js.map} +0 -0
- /package/dist/{chunk-XUJG2HFW.js.map → chunk-BH5BXLGI.js.map} +0 -0
- /package/dist/{chunk-4TXIZ5WI.js.map → chunk-CJUOBTLL.js.map} +0 -0
- /package/dist/{chunk-NWNSUHEB.js.map → chunk-CT5K725B.js.map} +0 -0
- /package/dist/{chunk-6OORHAE7.js.map → chunk-EQ7PWGBN.js.map} +0 -0
- /package/dist/{chunk-EJQW2IYW.js.map → chunk-FPFUXRD2.js.map} +0 -0
- /package/dist/{chunk-ZRPGVHTR.js.map → chunk-GGI7GM5O.js.map} +0 -0
- /package/dist/{chunk-KIHQE5YA.js.map → chunk-GVDJQMFI.js.map} +0 -0
- /package/dist/{chunk-TX5V42AY.js.map → chunk-HEBQS7QP.js.map} +0 -0
- /package/dist/{chunk-BUQ2JUND.js.map → chunk-JO6BBSGY.js.map} +0 -0
- /package/dist/{chunk-4ZV7N5CU.js.map → chunk-JXPHU3K5.js.map} +0 -0
- /package/dist/{chunk-TW3BXVEU.js.map → chunk-KMXAPYU2.js.map} +0 -0
- /package/dist/{chunk-CJBBGMIA.js.map → chunk-MNXVXW4T.js.map} +0 -0
- /package/dist/{chunk-BPLFM2GL.js.map → chunk-NYEA7JAI.js.map} +0 -0
- /package/dist/{chunk-O6AZDQJJ.js.map → chunk-OE2ZVL67.js.map} +0 -0
- /package/dist/{chunk-4523RYE7.js.map → chunk-OG2FP6SE.js.map} +0 -0
- /package/dist/{chunk-XKV5ZIBG.js.map → chunk-OGGPQVIG.js.map} +0 -0
- /package/dist/{chunk-SQNW22ZZ.js.map → chunk-OV4WREMW.js.map} +0 -0
- /package/dist/{chunk-6LGWJFQK.js.map → chunk-PFDM2OIP.js.map} +0 -0
- /package/dist/{chunk-H5VEJDEK.js.map → chunk-TVPPA4RD.js.map} +0 -0
- /package/dist/{chunk-ISH42UC7.js.map → chunk-USJPZ5Q7.js.map} +0 -0
- /package/dist/{chunk-5OIH36KX.js.map → chunk-V4FKHLEB.js.map} +0 -0
- /package/dist/{chunk-QPLWDSYG.js.map → chunk-WM6Y2S6W.js.map} +0 -0
- /package/dist/{chunk-EMMPNB6D.js.map → chunk-X4Z62ZQY.js.map} +0 -0
- /package/dist/{chunk-ZAB65STX.js.map → chunk-XHHR6G35.js.map} +0 -0
- /package/dist/{chunk-OP65D4UR.js.map → chunk-XR6WBW23.js.map} +0 -0
- /package/dist/{chunk-WIT3Y7JT.js.map → chunk-Y2B3I3IH.js.map} +0 -0
- /package/dist/{chunk-R6SNYC3D.js.map → chunk-Y3KRUT7O.js.map} +0 -0
- /package/dist/{chunk-AJ6XEO4E.js.map → chunk-ZUY65MFH.js.map} +0 -0
- /package/dist/{chunk-4X4KM4ZB.js.map → chunk-ZZMZF7PZ.js.map} +0 -0
- /package/dist/native/chunks/{chunk-ZAQ3AZJM.js.map → chunk-2R2R35C5.js.map} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { findGroup, type CategoryRowRef } from './category-tree';
|
|
2
2
|
import type { CategoryGroup } from './CategorySelect.types';
|
|
3
3
|
|
|
4
4
|
/** What a key handler is allowed to do to the panel. */
|
|
@@ -27,7 +27,7 @@ function activeRow(context: CategoryKeyContext): CategoryRowRef | undefined {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function groupOf(context: CategoryKeyContext, id: string): CategoryGroup | undefined {
|
|
30
|
-
return context.groups
|
|
30
|
+
return findGroup(context.groups, id);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
function moveCursor(context: CategoryKeyContext, delta: number): void {
|
|
@@ -40,41 +40,37 @@ function moveCursor(context: CategoryKeyContext, delta: number): void {
|
|
|
40
40
|
context.setActiveIndex(next);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/** → opens a
|
|
43
|
+
/** → opens a row that folds. On a leaf it is a no-op: there is nothing deeper. */
|
|
44
44
|
function handleRight(context: CategoryKeyContext): void {
|
|
45
45
|
const row = activeRow(context);
|
|
46
|
-
if (row?.
|
|
46
|
+
if (row?.branch) context.expand(row.id);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/** ← collapses a
|
|
49
|
+
/** ← collapses a foldable row, or walks a leaf back up to its parent. */
|
|
50
50
|
function handleLeft(context: CategoryKeyContext): void {
|
|
51
51
|
const row = activeRow(context);
|
|
52
52
|
if (!row) return;
|
|
53
|
-
if (row.
|
|
53
|
+
if (row.branch) {
|
|
54
54
|
context.collapse(row.id);
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
if (!row.parentId) return;
|
|
58
58
|
context.collapse(row.parentId);
|
|
59
|
-
const parentIndex = context.rows.findIndex(
|
|
60
|
-
(candidate) => candidate.kind === 'category' && candidate.id === row.parentId,
|
|
61
|
-
);
|
|
59
|
+
const parentIndex = context.rows.findIndex((candidate) => candidate.id === row.parentId);
|
|
62
60
|
if (parentIndex >= 0) context.setActiveIndex(parentIndex);
|
|
63
61
|
}
|
|
64
62
|
|
|
65
63
|
/**
|
|
66
|
-
* What a
|
|
64
|
+
* What a FOLDABLE row does when it is activated — the one answer the click, the
|
|
67
65
|
* Space key and (in single-select) Enter all take, so the three cannot disagree
|
|
68
66
|
* about a row the way they did over a childless category.
|
|
69
67
|
*
|
|
70
|
-
* A
|
|
71
|
-
*
|
|
72
|
-
*
|
|
68
|
+
* A row that is only a frame expands instead of marking: it has no control, so
|
|
69
|
+
* "mark" has nothing to mean there. A childless category is not a frame — it is
|
|
70
|
+
* the leaf — so it never reaches here.
|
|
73
71
|
*/
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
if (!group) return;
|
|
77
|
-
if (!context.allowParentSelection && !isLeafCategory(group)) {
|
|
72
|
+
function activateBranchRow(context: CategoryKeyContext, categoryId: string): void {
|
|
73
|
+
if (!context.allowParentSelection) {
|
|
78
74
|
context.toggleExpanded(categoryId);
|
|
79
75
|
return;
|
|
80
76
|
}
|
|
@@ -82,19 +78,24 @@ function activateCategoryRow(context: CategoryKeyContext, categoryId: string): v
|
|
|
82
78
|
context.pick(categoryId);
|
|
83
79
|
return;
|
|
84
80
|
}
|
|
85
|
-
context
|
|
81
|
+
const group = groupOf(context, categoryId);
|
|
82
|
+
if (group) context.toggleCategory(group);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Mark the row under the cursor — or fold it, when it is only a frame. */
|
|
86
|
+
function activateRow(context: CategoryKeyContext, row: CategoryRowRef): void {
|
|
87
|
+
if (row.branch) {
|
|
88
|
+
activateBranchRow(context, row.id);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (context.single) context.pick(row.id);
|
|
92
|
+
else context.toggleSubcategory(row.id);
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
/** Space marks the row under the cursor. */
|
|
89
96
|
function handleSpace(context: CategoryKeyContext): void {
|
|
90
97
|
const row = activeRow(context);
|
|
91
|
-
if (
|
|
92
|
-
if (row.kind === 'subcategory') {
|
|
93
|
-
if (context.single) context.pick(row.id);
|
|
94
|
-
else context.toggleSubcategory(row.id);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
activateCategoryRow(context, row.id);
|
|
98
|
+
if (row) activateRow(context, row);
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
function handleEnter(context: CategoryKeyContext): void {
|
|
@@ -103,12 +104,7 @@ function handleEnter(context: CategoryKeyContext): void {
|
|
|
103
104
|
context.commit();
|
|
104
105
|
return;
|
|
105
106
|
}
|
|
106
|
-
if (
|
|
107
|
-
if (row.kind === 'subcategory') {
|
|
108
|
-
context.pick(row.id);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
activateCategoryRow(context, row.id);
|
|
107
|
+
if (row) activateRow(context, row);
|
|
112
108
|
}
|
|
113
109
|
|
|
114
110
|
/** Key → behaviour. A map keeps this dispatch flat instead of a long if-chain. */
|
|
@@ -15,40 +15,64 @@ import type {
|
|
|
15
15
|
export function foldText(text: string): string {
|
|
16
16
|
return text
|
|
17
17
|
.normalize('NFD')
|
|
18
|
-
.replace(/[
|
|
18
|
+
.replace(/[̀-ͯ]/g, '')
|
|
19
19
|
.toLowerCase();
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Whether following `parentId` up from `startId` ever comes back to a node it
|
|
24
|
+
* already passed.
|
|
24
25
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
26
|
+
* Two levels made a cycle impossible by construction: a child was attached to a
|
|
27
|
+
* ROOT or promoted, so nothing could ever point back down. An arbitrary depth
|
|
28
|
+
* attaches every node to whichever node its `parentId` names, and a payload
|
|
29
|
+
* where two rows name each other would build a ring that every walk below —
|
|
30
|
+
* flattening, filtering, counting leaves — follows until the stack gives out. So
|
|
31
|
+
* the chain is checked rather than trusted, and a node on a ring is promoted to
|
|
32
|
+
* top level, which is what {@link buildCategoryGroups} already does with a
|
|
33
|
+
* parent it cannot resolve.
|
|
28
34
|
*/
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
function onParentCycle(startId: string, parents: ReadonlyMap<string, string>): boolean {
|
|
36
|
+
const seen = new Set<string>([startId]);
|
|
37
|
+
let current = parents.get(startId);
|
|
38
|
+
while (current !== undefined) {
|
|
39
|
+
if (seen.has(current)) return true;
|
|
40
|
+
seen.add(current);
|
|
41
|
+
current = parents.get(current);
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
36
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Nest a flat `parentId` list into a tree of ANY depth, preserving input order
|
|
48
|
+
* at every level.
|
|
49
|
+
*
|
|
50
|
+
* An option whose `parentId` names nothing in the payload is promoted to top
|
|
51
|
+
* level rather than dropped — a category is never invisible just because its
|
|
52
|
+
* parent was filtered out of the response. A duplicate id keeps its first row,
|
|
53
|
+
* so a repeated payload cannot place the same node twice.
|
|
54
|
+
*/
|
|
55
|
+
export function buildCategoryGroups(options: CategorySelectOption[]): CategoryGroup[] {
|
|
56
|
+
const nodes = new Map<string, CategoryGroup>();
|
|
57
|
+
const parents = new Map<string, string>();
|
|
37
58
|
options.forEach((option) => {
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
if (
|
|
41
|
-
parent.subcategories.push(option);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
if (!rootIds.has(option.id)) orphans.push({ category: option, subcategories: [] });
|
|
59
|
+
if (nodes.has(option.id)) return;
|
|
60
|
+
nodes.set(option.id, { category: option, subcategories: [] });
|
|
61
|
+
if (option.parentId && option.parentId !== option.id) parents.set(option.id, option.parentId);
|
|
45
62
|
});
|
|
46
63
|
|
|
47
|
-
|
|
64
|
+
const roots: CategoryGroup[] = [];
|
|
65
|
+
nodes.forEach((node, id) => {
|
|
66
|
+
const parentId = parents.get(id);
|
|
67
|
+
const parent = parentId === undefined ? undefined : nodes.get(parentId);
|
|
68
|
+
if (parent && !onParentCycle(id, parents)) parent.subcategories.push(node);
|
|
69
|
+
else roots.push(node);
|
|
70
|
+
});
|
|
71
|
+
return roots;
|
|
48
72
|
}
|
|
49
73
|
|
|
50
74
|
/**
|
|
51
|
-
* Whether this
|
|
75
|
+
* Whether this node IS the leaf, because nothing sits under it.
|
|
52
76
|
*
|
|
53
77
|
* The leaf-only default makes a category a heading and the subcategory the thing
|
|
54
78
|
* you pick — but a childless category has no subcategory to offer instead, so
|
|
@@ -62,14 +86,66 @@ export function isLeafCategory(group: CategoryGroup): boolean {
|
|
|
62
86
|
return group.subcategories.length === 0;
|
|
63
87
|
}
|
|
64
88
|
|
|
65
|
-
/** Every selectable leaf id across
|
|
89
|
+
/** Every selectable leaf id across the whole tree, in display order. */
|
|
66
90
|
export function collectLeafIds(groups: CategoryGroup[]): string[] {
|
|
67
91
|
return groups.flatMap((group) => leavesOf(group));
|
|
68
92
|
}
|
|
69
93
|
|
|
70
|
-
/**
|
|
94
|
+
/**
|
|
95
|
+
* The leaves a node stands for — every leaf BELOW it, or itself when childless.
|
|
96
|
+
*
|
|
97
|
+
* Recursive rather than one level down, so a category three levels deep still
|
|
98
|
+
* stands for the items at the bottom of it and not for the subcategories in
|
|
99
|
+
* between: the value a caller receives stays uniformly leaves, which is the
|
|
100
|
+
* property that lets a chip, a tri-state checkbox and `Marcar tudo` agree.
|
|
101
|
+
*/
|
|
71
102
|
export function leavesOf(group: CategoryGroup): string[] {
|
|
72
|
-
|
|
103
|
+
if (isLeafCategory(group)) return [group.category.id];
|
|
104
|
+
return group.subcategories.flatMap((sub) => leavesOf(sub));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Every node that can be unfolded, at any depth — what "Expandir tudo" opens. */
|
|
108
|
+
export function collectBranchIds(groups: CategoryGroup[]): string[] {
|
|
109
|
+
return groups.flatMap((group) =>
|
|
110
|
+
isLeafCategory(group) ? [] : [group.category.id, ...collectBranchIds(group.subcategories)],
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The node carrying `id`, searched at every depth. */
|
|
115
|
+
export function findGroup(groups: CategoryGroup[], id: string): CategoryGroup | undefined {
|
|
116
|
+
for (const group of groups) {
|
|
117
|
+
if (group.category.id === id) return group;
|
|
118
|
+
const found = findGroup(group.subcategories, id);
|
|
119
|
+
if (found) return found;
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The names from the top-level category down to `id`, or `null` when absent.
|
|
126
|
+
*
|
|
127
|
+
* The single-select trigger reads this back as `Pai › Filha › Item`, so a chosen
|
|
128
|
+
* row keeps the context that tells `Massas` (mercearia) from `Massas` (pratos
|
|
129
|
+
* principais) once the panel has closed over it.
|
|
130
|
+
*/
|
|
131
|
+
export function categoryPath(groups: CategoryGroup[], id: string): string[] | null {
|
|
132
|
+
for (const group of groups) {
|
|
133
|
+
if (group.category.id === id) return [group.category.name];
|
|
134
|
+
const below = categoryPath(group.subcategories, id);
|
|
135
|
+
if (below) return [group.category.name, ...below];
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* How many levels the deepest branch has. A flat list is 1, the classic
|
|
142
|
+
* category/subcategory tree is 2, anything more is a tree with items in it.
|
|
143
|
+
*/
|
|
144
|
+
export function treeDepth(groups: CategoryGroup[]): number {
|
|
145
|
+
return groups.reduce(
|
|
146
|
+
(deepest, group) => Math.max(deepest, 1 + treeDepth(group.subcategories)),
|
|
147
|
+
0,
|
|
148
|
+
);
|
|
73
149
|
}
|
|
74
150
|
|
|
75
151
|
export type CategoryCheckState = 'off' | 'partial' | 'on';
|
|
@@ -105,9 +181,12 @@ export function toggleLeaf(id: string, selected: ReadonlySet<string>): Set<strin
|
|
|
105
181
|
}
|
|
106
182
|
|
|
107
183
|
/**
|
|
108
|
-
* Collapse a raw id set into the chips a human reads: a
|
|
109
|
-
* selected becomes ONE chip bearing
|
|
110
|
-
*
|
|
184
|
+
* Collapse a raw id set into the chips a human reads: a node that is fully
|
|
185
|
+
* selected becomes ONE chip bearing its name, rather than one chip per leaf —
|
|
186
|
+
* "Bebidas" instead of six drinks.
|
|
187
|
+
*
|
|
188
|
+
* A partly-selected node hands the question down to its own children, so the
|
|
189
|
+
* chip that appears is always the DEEPEST node that is completely selected.
|
|
111
190
|
*/
|
|
112
191
|
export function summarizeSelection(
|
|
113
192
|
groups: CategoryGroup[],
|
|
@@ -117,17 +196,20 @@ export function summarizeSelection(
|
|
|
117
196
|
const state = categoryCheckState(group, selected);
|
|
118
197
|
if (state === 'off') return [];
|
|
119
198
|
if (state === 'on') {
|
|
120
|
-
|
|
199
|
+
// `whole` says the chip stands for MORE than itself, so it is the node
|
|
200
|
+
// having children that decides it — not the node being fully selected,
|
|
201
|
+
// which a leaf always is the moment it is picked at all.
|
|
202
|
+
return [
|
|
203
|
+
{ id: group.category.id, label: group.category.name, whole: !isLeafCategory(group) },
|
|
204
|
+
];
|
|
121
205
|
}
|
|
122
|
-
return group.subcategories
|
|
123
|
-
.filter((sub) => selected.has(sub.id))
|
|
124
|
-
.map((sub) => ({ id: sub.id, label: sub.name, whole: false }));
|
|
206
|
+
return summarizeSelection(group.subcategories, selected);
|
|
125
207
|
});
|
|
126
208
|
}
|
|
127
209
|
|
|
128
210
|
/**
|
|
129
|
-
* Drop the leaves a chip stands for. A
|
|
130
|
-
*
|
|
211
|
+
* Drop the leaves a chip stands for. A chip naming a node removes everything
|
|
212
|
+
* under it; a chip naming nothing in the tree removes just that id.
|
|
131
213
|
*/
|
|
132
214
|
export function removeChip(
|
|
133
215
|
groups: CategoryGroup[],
|
|
@@ -135,7 +217,7 @@ export function removeChip(
|
|
|
135
217
|
selected: ReadonlySet<string>,
|
|
136
218
|
): Set<string> {
|
|
137
219
|
const next = new Set(selected);
|
|
138
|
-
const group = groups
|
|
220
|
+
const group = findGroup(groups, chipId);
|
|
139
221
|
if (group) {
|
|
140
222
|
leavesOf(group).forEach((id) => next.delete(id));
|
|
141
223
|
return next;
|
|
@@ -145,23 +227,23 @@ export function removeChip(
|
|
|
145
227
|
}
|
|
146
228
|
|
|
147
229
|
/**
|
|
148
|
-
* Search
|
|
230
|
+
* Search every level, keeping each hit under its parents.
|
|
149
231
|
*
|
|
150
|
-
* A
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
232
|
+
* A node that matches keeps its whole subtree, so "Bebidas" shows the entire
|
|
233
|
+
* group. A node that does not match keeps only the descendants that do — the hit
|
|
234
|
+
* still arrives with its parents visible above it, which is the context that
|
|
235
|
+
* tells "Massas" (mercearia) from "Massas" (pratos principais).
|
|
154
236
|
*/
|
|
155
237
|
export function filterCategoryGroups(groups: CategoryGroup[], query: string): CategoryGroup[] {
|
|
156
238
|
const needle = foldText(query.trim());
|
|
157
239
|
if (!needle) return groups;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
240
|
+
const walk = (nodes: CategoryGroup[]): CategoryGroup[] =>
|
|
241
|
+
nodes.flatMap((node) => {
|
|
242
|
+
if (foldText(node.category.name).includes(needle)) return [node];
|
|
243
|
+
const subcategories = walk(node.subcategories);
|
|
244
|
+
return subcategories.length > 0 ? [{ category: node.category, subcategories }] : [];
|
|
245
|
+
});
|
|
246
|
+
return walk(groups);
|
|
165
247
|
}
|
|
166
248
|
|
|
167
249
|
/** A run of text, flagged when it is the part that matched the query. */
|
|
@@ -191,29 +273,27 @@ export function highlightSegments(text: string, query: string): HighlightSegment
|
|
|
191
273
|
|
|
192
274
|
/** A flattened row, so keyboard navigation can walk what is actually on screen. */
|
|
193
275
|
export interface CategoryRowRef {
|
|
194
|
-
kind: 'category' | 'subcategory';
|
|
195
276
|
id: string;
|
|
196
|
-
/**
|
|
277
|
+
/** 0 for a top-level category, one more for each level below it. */
|
|
278
|
+
depth: number;
|
|
279
|
+
/** True when the row has children to unfold — what → and ← act on. */
|
|
280
|
+
branch: boolean;
|
|
281
|
+
/** Owning node id — lets ← jump from a child back to its parent. */
|
|
197
282
|
parentId?: string;
|
|
198
283
|
}
|
|
199
284
|
|
|
200
|
-
/** The visible rows, in order, honouring which
|
|
285
|
+
/** The visible rows, in order, honouring which nodes are expanded. */
|
|
201
286
|
export function flattenRows(
|
|
202
287
|
groups: CategoryGroup[],
|
|
203
288
|
isExpanded: (categoryId: string) => boolean,
|
|
204
289
|
): CategoryRowRef[] {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
parentId: group.category.id,
|
|
215
|
-
}),
|
|
216
|
-
),
|
|
217
|
-
];
|
|
218
|
-
});
|
|
290
|
+
const walk = (nodes: CategoryGroup[], depth: number, parentId?: string): CategoryRowRef[] =>
|
|
291
|
+
nodes.flatMap((node) => {
|
|
292
|
+
const id = node.category.id;
|
|
293
|
+
const branch = !isLeafCategory(node);
|
|
294
|
+
const row: CategoryRowRef = { id, depth, branch, ...(parentId ? { parentId } : {}) };
|
|
295
|
+
if (!branch || !isExpanded(id)) return [row];
|
|
296
|
+
return [row, ...walk(node.subcategories, depth + 1, id)];
|
|
297
|
+
});
|
|
298
|
+
return walk(groups, 0);
|
|
219
299
|
}
|
|
@@ -11,9 +11,13 @@ export type {
|
|
|
11
11
|
export {
|
|
12
12
|
buildCategoryGroups,
|
|
13
13
|
categoryCheckState,
|
|
14
|
+
categoryPath,
|
|
15
|
+
collectBranchIds,
|
|
14
16
|
collectLeafIds,
|
|
15
17
|
filterCategoryGroups,
|
|
18
|
+
findGroup,
|
|
16
19
|
foldText,
|
|
17
20
|
leavesOf,
|
|
18
21
|
summarizeSelection,
|
|
22
|
+
treeDepth,
|
|
19
23
|
} from './category-tree';
|
|
@@ -2,10 +2,12 @@ import { useCallback, useMemo, useRef, useState } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
buildCategoryGroups,
|
|
5
|
+
collectBranchIds,
|
|
5
6
|
filterCategoryGroups,
|
|
6
7
|
flattenRows,
|
|
7
8
|
toggleCategoryLeaves,
|
|
8
9
|
toggleLeaf,
|
|
10
|
+
treeDepth,
|
|
9
11
|
type CategoryRowRef,
|
|
10
12
|
} from './category-tree';
|
|
11
13
|
import type { CategoryGroup, CategorySelectOption } from './CategorySelect.types';
|
|
@@ -44,13 +46,20 @@ function useExpandedCategories(
|
|
|
44
46
|
});
|
|
45
47
|
}, []);
|
|
46
48
|
|
|
49
|
+
// Every foldable node at EVERY depth, so "Expandir tudo" opens the tree rather
|
|
50
|
+
// than only its first storey.
|
|
47
51
|
const setAllExpanded = useCallback(
|
|
48
52
|
(shouldExpand: boolean) => {
|
|
49
|
-
setExpanded(shouldExpand ? new Set(allGroups
|
|
53
|
+
setExpanded(shouldExpand ? new Set(collectBranchIds(allGroups)) : new Set());
|
|
50
54
|
},
|
|
51
55
|
[allGroups],
|
|
52
56
|
);
|
|
53
57
|
|
|
58
|
+
// Opening unfolds the TOP level only. On a two-level tree that is the whole
|
|
59
|
+
// thing on show, which is what the panel has always done; on a deeper one it
|
|
60
|
+
// is the categories and their subcategories, with the items behind one more
|
|
61
|
+
// click — the alternative being the flat list the tree replaces. A search
|
|
62
|
+
// still reveals every hit: `searching` short-circuits the lookup above.
|
|
54
63
|
const expandAll = useCallback((groups: CategoryGroup[]) => {
|
|
55
64
|
setExpanded(new Set(groups.map((group) => group.category.id)));
|
|
56
65
|
}, []);
|
|
@@ -90,6 +99,8 @@ export interface CategorySelectState extends ExpansionState, DraftState {
|
|
|
90
99
|
visibleGroups: CategoryGroup[];
|
|
91
100
|
/** All groups, unfiltered — the basis for chips and "select all". */
|
|
92
101
|
allGroups: CategoryGroup[];
|
|
102
|
+
/** True when the tree runs deeper than category/subcategory. */
|
|
103
|
+
deepTree: boolean;
|
|
93
104
|
rows: CategoryRowRef[];
|
|
94
105
|
activeIndex: number;
|
|
95
106
|
setQuery: (next: string) => void;
|
|
@@ -128,6 +139,8 @@ export function useCategorySelect({
|
|
|
128
139
|
[allGroups, query],
|
|
129
140
|
);
|
|
130
141
|
|
|
142
|
+
const deepTree = useMemo(() => treeDepth(allGroups) > 2, [allGroups]);
|
|
143
|
+
|
|
131
144
|
const expansion = useExpandedCategories(allGroups, query.trim().length > 0);
|
|
132
145
|
const selection = useDraftSelection(selected);
|
|
133
146
|
const rows = useMemo(
|
|
@@ -161,6 +174,7 @@ export function useCategorySelect({
|
|
|
161
174
|
query,
|
|
162
175
|
visibleGroups,
|
|
163
176
|
allGroups,
|
|
177
|
+
deepTree,
|
|
164
178
|
rows,
|
|
165
179
|
activeIndex,
|
|
166
180
|
setQuery,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/form/CategorySelect/useCategorySelect.ts"],"sourcesContent":["import { useCallback, useMemo, useRef, useState } from 'react';\n\nimport {\n buildCategoryGroups,\n filterCategoryGroups,\n flattenRows,\n toggleCategoryLeaves,\n toggleLeaf,\n type CategoryRowRef,\n} from './category-tree';\nimport type { CategoryGroup, CategorySelectOption } from './CategorySelect.types';\n\n/** Which categories are unfolded, and the ways that set changes. */\ninterface ExpansionState {\n isExpanded: (categoryId: string) => boolean;\n toggleExpanded: (categoryId: string) => void;\n setAllExpanded: (expanded: boolean) => void;\n expandAll: (groups: CategoryGroup[]) => void;\n}\n\n/**\n * The expanded set.\n *\n * `searching` short-circuits the lookup instead of mutating the set: a query must\n * reveal every hit, but clearing it has to restore whatever the admin had folded,\n * which an overwrite would have destroyed.\n */\nfunction useExpandedCategories(\n allGroups: CategoryGroup[],\n searching: boolean,\n): ExpansionState {\n const [expanded, setExpanded] = useState<ReadonlySet<string>>(() => new Set());\n\n const isExpanded = useCallback(\n (categoryId: string) => searching || expanded.has(categoryId),\n [searching, expanded],\n );\n\n const toggleExpanded = useCallback((categoryId: string) => {\n setExpanded((current) => {\n const next = new Set(current);\n if (!next.delete(categoryId)) next.add(categoryId);\n return next;\n });\n }, []);\n\n const setAllExpanded = useCallback(\n (shouldExpand: boolean) => {\n setExpanded(shouldExpand ? new Set(allGroups.map((g) => g.category.id)) : new Set());\n },\n [allGroups],\n );\n\n const expandAll = useCallback((groups: CategoryGroup[]) => {\n setExpanded(new Set(groups.map((group) => group.category.id)));\n }, []);\n\n return { isExpanded, toggleExpanded, setAllExpanded, expandAll };\n}\n\n/** The pending selection and the two ways a row changes it. */\ninterface DraftState {\n draft: ReadonlySet<string>;\n setDraft: (next: ReadonlySet<string>) => void;\n toggleCategory: (group: CategoryGroup) => void;\n toggleSubcategory: (id: string) => void;\n}\n\nfunction useDraftSelection(initial: readonly string[]): DraftState {\n const [draft, setDraftState] = useState<ReadonlySet<string>>(() => new Set(initial));\n\n const toggleCategory = useCallback((group: CategoryGroup) => {\n setDraftState((current) => toggleCategoryLeaves(group, current));\n }, []);\n\n const toggleSubcategory = useCallback((id: string) => {\n setDraftState((current) => toggleLeaf(id, current));\n }, []);\n\n const setDraft = useCallback((next: ReadonlySet<string>) => setDraftState(next), []);\n\n return { draft, setDraft, toggleCategory, toggleSubcategory };\n}\n\n/** Everything the panel and trigger need to render and drive one select. */\nexport interface CategorySelectState extends ExpansionState, DraftState {\n open: boolean;\n query: string;\n /** Groups after the search filter — what is actually on screen. */\n visibleGroups: CategoryGroup[];\n /** All groups, unfiltered — the basis for chips and \"select all\". */\n allGroups: CategoryGroup[];\n rows: CategoryRowRef[];\n activeIndex: number;\n setQuery: (next: string) => void;\n setActiveIndex: (next: number) => void;\n openPanel: () => void;\n closePanel: () => void;\n triggerRef: React.RefObject<HTMLButtonElement | null>;\n}\n\ninterface UseCategorySelectArgs {\n options: CategorySelectOption[];\n /** The applied selection to seed the draft from each time the panel opens. */\n selected: readonly string[];\n}\n\n/**\n * Panel state for one CategorySelect.\n *\n * The DRAFT is the load-bearing idea. Ticks land here, not on the caller's\n * `value`, so the list behind the panel does not refetch on every click; Apply\n * publishes, dismissing discards. Opening re-seeds the draft from the applied\n * value, which is what makes \"cancel\" mean cancel.\n */\nexport function useCategorySelect({\n options,\n selected,\n}: UseCategorySelectArgs): CategorySelectState {\n const [open, setOpen] = useState(false);\n const [query, setQueryState] = useState('');\n const [activeIndex, setActiveIndex] = useState(-1);\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n\n const allGroups = useMemo(() => buildCategoryGroups(options), [options]);\n const visibleGroups = useMemo(\n () => filterCategoryGroups(allGroups, query),\n [allGroups, query],\n );\n\n const expansion = useExpandedCategories(allGroups, query.trim().length > 0);\n const selection = useDraftSelection(selected);\n const rows = useMemo(\n () => flattenRows(visibleGroups, expansion.isExpanded),\n [visibleGroups, expansion.isExpanded],\n );\n\n const openPanel = useCallback(() => {\n selection.setDraft(new Set(selected));\n expansion.expandAll(allGroups);\n setQueryState('');\n setActiveIndex(-1);\n setOpen(true);\n }, [selected, allGroups, selection, expansion]);\n\n const closePanel = useCallback(() => {\n setOpen(false);\n setQueryState('');\n setActiveIndex(-1);\n }, []);\n\n const setQuery = useCallback((next: string) => {\n setQueryState(next);\n setActiveIndex(-1);\n }, []);\n\n return {\n ...expansion,\n ...selection,\n open,\n query,\n visibleGroups,\n allGroups,\n rows,\n activeIndex,\n setQuery,\n setActiveIndex,\n openPanel,\n closePanel,\n triggerRef,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/form/Button/Button.metrics.ts"],"sourcesContent":["import { darken, lighten } from '../../../tokens/color';\nimport type { ColorValue, SizeValue } from '../../../tokens/vocabulary';\nimport type { UiPaletteColor, UiTheme } from '../../../tokens/theme';\n\n/**\n * THE NUMBERS BOTH `Button` RENDERERS DRAW WITH.\n *\n * `Button.styles.ts` (web, emotion) and `Button.native.tsx` (React Native)\n * read this one table. The web turns the px into rem and the padding into a\n * shorthand string; native uses the numbers as they are. Neither restates a\n * value, which is what keeps a native `md` button 16px tall of padding and\n * 16px of type exactly like the web one.\n */\nexport interface ButtonSizeMetrics {\n paddingVertical: number;\n paddingHorizontal: number;\n fontSize: number;\n}\n\nexport const BUTTON_SIZES: Record<SizeValue, ButtonSizeMetrics> = {\n xs: { paddingVertical: 2, paddingHorizontal: 8, fontSize: 12 },\n sm: { paddingVertical: 6, paddingHorizontal: 12, fontSize: 14 },\n md: { paddingVertical: 8, paddingHorizontal: 16, fontSize: 16 },\n lg: { paddingVertical: 10, paddingHorizontal: 20, fontSize: 18 },\n xl: { paddingVertical: 12, paddingHorizontal: 24, fontSize: 20 },\n};\n\n/** A button that is only an icon is square: uniform padding, no min-width. */\nexport const ICON_ONLY_PADDING: Record<SizeValue, number> = {\n xs: 2,\n sm: 5,\n md: 7,\n lg: 9,\n xl: 11,\n};\n\nexport const BUTTON_FONT_WEIGHT = 500;\n/** `theme.spacing(1)`. */\nexport const BUTTON_RADIUS_UNITS = 1;\n/** Between the icon and the label: `theme.spacing(0.5)`. */\nexport const BUTTON_ICON_GAP_UNITS = 0.5;\nexport const BUTTON_SPINNER_SIZE = 16;\n/** MUI's `line-height` for button text. */\nexport const BUTTON_LINE_HEIGHT = 1.75;\n\n/** The wash behind an outline, ghost or text button while it is hovered or pressed. */\nexport const BUTTON_WASH_ALPHA = 0.1;\nexport const GLASS_BACKGROUND_ALPHA = 0.1;\nexport const GLASS_BACKGROUND_ALPHA_PRESSED = 0.2;\nexport const GLASS_BORDER_ALPHA = 0.2;\nexport const GLOW_INNER_ALPHA = 0.6;\nexport const GLOW_OUTER_ALPHA = 0.3;\nexport const GLOW_RADIUS = 20;\nexport const PULSE_ALPHA = 0.3;\nexport const PULSE_DURATION_MS = 2000;\nexport const PULSE_SPREAD = 15;\n\n/**\n * `neutral` has no palette slot of its own in MUI, so `Button.styles.ts` has\n * always drawn it from three greys. Same three greys here.\n */\nexport function buttonPalette(theme: UiTheme, color: ColorValue): UiPaletteColor {\n if (color === 'neutral') {\n return {\n main: theme.palette.grey[700],\n dark: theme.palette.grey[800],\n light: theme.palette.grey[500],\n contrastText: '#fff',\n };\n }\n return theme.palette[color];\n}\n\n/**\n * The label colour for `ghost` and `text`: one step darker than `main` in\n * light mode (lighter in dark), because `main` is chosen to sit UNDER white and\n * is not legible AS text on a tinted surface. See the web `quietInk`.\n */\nexport const quietInk = (theme: UiTheme, palette: UiPaletteColor): string =>\n theme.mode === 'dark' ? lighten(palette.main, 0.35) : darken(palette.main, 0.3);\n\n/** Gradients run between two related hues; the named colours pick a specific pair. */\nexport function gradientStops(\n theme: UiTheme,\n color: ColorValue,\n palette: UiPaletteColor,\n): [string, string] {\n switch (color) {\n case 'primary':\n return [theme.palette.primary.main, theme.palette.secondary.main];\n case 'secondary':\n return [theme.palette.secondary.main, theme.palette.primary.main];\n case 'success':\n case 'warning':\n return [theme.palette[color].light, theme.palette[color].dark];\n case 'danger':\n return [theme.palette.danger.light, theme.palette.danger.dark];\n default:\n return [palette.main, palette.dark];\n }\n}\n"],"mappings":";;;;;;AAmBO,IAAM,eAAqD;AAAA,EAChE,IAAI,EAAE,iBAAiB,GAAG,mBAAmB,GAAG,UAAU,GAAG;AAAA,EAC7D,IAAI,EAAE,iBAAiB,GAAG,mBAAmB,IAAI,UAAU,GAAG;AAAA,EAC9D,IAAI,EAAE,iBAAiB,GAAG,mBAAmB,IAAI,UAAU,GAAG;AAAA,EAC9D,IAAI,EAAE,iBAAiB,IAAI,mBAAmB,IAAI,UAAU,GAAG;AAAA,EAC/D,IAAI,EAAE,iBAAiB,IAAI,mBAAmB,IAAI,UAAU,GAAG;AACjE;AAGO,IAAM,oBAA+C;AAAA,EAC1D,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEO,IAAM,qBAAqB;AAE3B,IAAM,sBAAsB;AAE5B,IAAM,wBAAwB;AAC9B,IAAM,sBAAsB;AAE5B,IAAM,qBAAqB;AAG3B,IAAM,oBAAoB;AAC1B,IAAM,yBAAyB;AAC/B,IAAM,iCAAiC;AACvC,IAAM,qBAAqB;AAC3B,IAAM,mBAAmB;AACzB,IAAM,mBAAmB;AACzB,IAAM,cAAc;AACpB,IAAM,cAAc;AACpB,IAAM,oBAAoB;AAC1B,IAAM,eAAe;AAMrB,SAAS,cAAc,OAAgB,OAAmC;AAC/E,MAAI,UAAU,WAAW;AACvB,WAAO;AAAA,MACL,MAAM,MAAM,QAAQ,KAAK,GAAG;AAAA,MAC5B,MAAM,MAAM,QAAQ,KAAK,GAAG;AAAA,MAC5B,OAAO,MAAM,QAAQ,KAAK,GAAG;AAAA,MAC7B,cAAc;AAAA,IAChB;AAAA,EACF;AACA,SAAO,MAAM,QAAQ,KAAK;AAC5B;AAOO,IAAM,WAAW,CAAC,OAAgB,YACvC,MAAM,SAAS,SAAS,QAAQ,QAAQ,MAAM,IAAI,IAAI,OAAO,QAAQ,MAAM,GAAG;AAGzE,SAAS,cACd,OACA,OACA,SACkB;AAClB,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO,CAAC,MAAM,QAAQ,QAAQ,MAAM,MAAM,QAAQ,UAAU,IAAI;AAAA,IAClE,KAAK;AACH,aAAO,CAAC,MAAM,QAAQ,UAAU,MAAM,MAAM,QAAQ,QAAQ,IAAI;AAAA,IAClE,KAAK;AAAA,IACL,KAAK;AACH,aAAO,CAAC,MAAM,QAAQ,KAAK,EAAE,OAAO,MAAM,QAAQ,KAAK,EAAE,IAAI;AAAA,IAC/D,KAAK;AACH,aAAO,CAAC,MAAM,QAAQ,OAAO,OAAO,MAAM,QAAQ,OAAO,IAAI;AAAA,IAC/D;AACE,aAAO,CAAC,QAAQ,MAAM,QAAQ,IAAI;AAAA,EACtC;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/form/CategorySelect/CategorySelect.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useMemo, useRef } from 'react';\nimport { useTheme } from '@mui/material/styles/index.js';\nimport useMediaQuery from '@mui/material/useMediaQuery/index.js';\n\nimport { summarizeSelection } from './category-tree';\nimport { CategoryPanel } from './CategoryPanel';\nimport { CategoryPanelSurface } from './CategoryPanelSurface';\nimport { CategoryTrigger } from './CategoryTrigger';\nimport { METRICS } from './CategorySelect.styles';\nimport type { CategoryGroup, CategorySelectProps } from './CategorySelect.types';\nimport { useCategoryCommit } from './useCategoryCommit';\nimport { useCategoryKeyHandler } from './useCategoryKeyHandler';\nimport { useCategorySelect } from './useCategorySelect';\nimport { FormControl, FormLabel, FormMessage } from '../Form';\n\n/** The applied selection, always as an array — the two modes differ in shape. */\nfunction appliedIds(props: CategorySelectProps): string[] {\n if (props.mode === 'single') return props.value ? [props.value] : [];\n return props.value;\n}\n\n/** True when the draft differs from what is already applied. */\nfunction draftChanged(draft: ReadonlySet<string>, applied: readonly string[]): boolean {\n return draft.size !== applied.length || applied.some((id) => !draft.has(id));\n}\n\n/** \"Pai › Filha\" for a chosen id, so the trigger carries its context. */\nfunction singleLabel(groups: CategoryGroup[], value: string | null): string | undefined {\n if (!value) return undefined;\n const asCategory = groups.find((group) => group.category.id === value);\n if (asCategory) return asCategory.category.name;\n const owner = groups.find((group) => group.subcategories.some((sub) => sub.id === value));\n const sub = owner?.subcategories.find((candidate) => candidate.id === value);\n return sub && owner ? `${owner.category.name} › ${sub.name}` : undefined;\n}\n\n/** The optional props, resolved once so the component body stays branch-free. */\ninterface ResolvedConfig {\n single: boolean;\n placeholder: string;\n disabled: boolean;\n loading: boolean;\n fullWidth: boolean;\n showCounts: boolean;\n allowParentSelection: boolean;\n dataTestId: string;\n}\n\nfunction resolveConfig(props: CategorySelectProps): ResolvedConfig {\n const single = props.mode === 'single';\n return {\n single,\n placeholder: props.placeholder ?? (single ? props.copy.placeholderSingle : props.copy.placeholderMulti),\n disabled: props.disabled ?? false,\n loading: props.loading ?? false,\n fullWidth: props.fullWidth ?? false,\n showCounts: props.showCounts ?? false,\n allowParentSelection: props.allowParentSelection ?? false,\n dataTestId: props.dataTestId ?? 'category-select',\n };\n}\n\n/** Focus the search box on open, and keep the keyboard cursor in view. */\nfunction usePanelFocus(\n open: boolean,\n activeIndex: number,\n searchInputRef: React.RefObject<HTMLInputElement | null>,\n listRef: React.RefObject<HTMLDivElement | null>,\n): void {\n // The first thing an admin does with ten categories is type; focusing here\n // saves the extra click every single time the panel opens.\n useEffect(() => {\n if (!open) return undefined;\n const frame = requestAnimationFrame(() => searchInputRef.current?.focus());\n return () => cancelAnimationFrame(frame);\n }, [open, searchInputRef]);\n\n useEffect(() => {\n if (activeIndex < 0) return;\n const rows = listRef.current?.querySelectorAll('[role=\"option\"], [role=\"button\"]');\n rows?.[activeIndex]?.scrollIntoView({ block: 'nearest' });\n }, [activeIndex, listRef]);\n}\n\n/**\n * A hierarchical category picker: categories as the frame, subcategories as the\n * thing you choose.\n *\n * Two modes off one component. `multi` is the FILTER — ticks accumulate in a\n * draft and reach `onChange` only on Apply, so the list behind the panel does not\n * reload on every click. `single` is the \"move to…\" picker, where choosing a row\n * commits immediately because there is nothing to batch.\n *\n * The tree opens fully expanded: nothing a search could match is hidden behind a\n * disclosure. Selected categories pin above the list so they survive scrolling\n * and searching, search is accent-insensitive (`agua` finds `Águas`), and the\n * whole tree is keyboard-drivable — ↑↓ to move, → to open, ← to close, Space to\n * mark, Enter to apply, Esc to cancel.\n *\n * Under {@link METRICS.sheetBreakpoint} the panel becomes a bottom sheet, which\n * is the only way a 290px-tall list stays usable one-handed.\n */\nexport function CategorySelect(props: CategorySelectProps): React.JSX.Element {\n const { options, label, error, onCreateCategory, copy } = props;\n const {\n single,\n placeholder,\n disabled,\n loading,\n fullWidth,\n showCounts,\n allowParentSelection,\n dataTestId,\n } = resolveConfig(props);\n\n const theme = useTheme();\n const sheet = useMediaQuery(theme.breakpoints.down(METRICS.sheetBreakpoint));\n const applied = appliedIds(props);\n\n const state = useCategorySelect({ options, selected: applied });\n const searchInputRef = useRef<HTMLInputElement | null>(null);\n const listRef = useRef<HTMLDivElement | null>(null);\n usePanelFocus(state.open, state.activeIndex, searchInputRef, listRef);\n\n const chips = useMemo(\n () => summarizeSelection(state.allGroups, state.draft),\n [state.allGroups, state.draft],\n );\n const rowIds = useMemo(() => state.rows.map((row) => row.id), [state.rows]);\n\n const actions = useCategoryCommit({ props, state, single, allowParentSelection });\n const onKeyDown = useCategoryKeyHandler({ state, single, allowParentSelection, actions });\n\n return (\n <FormControl fullWidth={fullWidth} error={Boolean(error)}>\n {label && <FormLabel error={Boolean(error)}>{label}</FormLabel>}\n <CategoryTrigger copy={copy}\n placeholder={placeholder}\n selectionLabel={\n props.mode === 'single' ? singleLabel(state.allGroups, props.value) : undefined\n }\n count={single ? undefined : applied.length}\n open={state.open}\n disabled={disabled}\n fullWidth={fullWidth}\n onOpen={state.openPanel}\n onClear={applied.length > 0 ? actions.clearSelection : undefined}\n triggerRef={state.triggerRef}\n dataTestId={dataTestId}\n />\n <CategoryPanelSurface\n open={state.open}\n sheet={sheet}\n anchorEl={state.triggerRef.current}\n onClose={actions.cancel}\n >\n <CategoryPanel copy={copy}\n state={state}\n chips={chips}\n rowIds={rowIds}\n changed={draftChanged(state.draft, applied)}\n single={single}\n sheet={sheet}\n loading={loading}\n showCounts={showCounts}\n allowParentSelection={allowParentSelection}\n ariaLabel={label ?? placeholder}\n onKeyDown={onKeyDown}\n onActivateCategory={actions.activateCategory}\n onActivateSubcategory={actions.activateSubcategory}\n onApply={actions.commit}\n onCancel={actions.cancel}\n onCreateCategory={onCreateCategory}\n searchInputRef={searchInputRef}\n listRef={listRef}\n dataTestId={dataTestId}\n />\n </CategoryPanelSurface>\n {error && <FormMessage error dataTestId={`${dataTestId}-message`}>{error}</FormMessage>}\n </FormControl>\n );\n}\n"],"mappings":"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","names":["sub"]}
|