@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.
Files changed (221) hide show
  1. package/components-catalog.md +1 -1
  2. package/dist/_internal/components/data-display/CardKit/CardKebab.js +4 -4
  3. package/dist/_internal/components/data-display/CardKit/use-remove-confirm.js +4 -4
  4. package/dist/_internal/components/data-display/CardKit/use-row-confirm.js +4 -4
  5. package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +34 -34
  6. package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +40 -40
  7. package/dist/_internal/components/data-display/DataViews/DeleteViewDialog.js +4 -4
  8. package/dist/_internal/components/data-display/DataViews/ManageViewsDialog.js +5 -5
  9. package/dist/_internal/components/data-display/DataViews/SaveViewModal.js +4 -4
  10. package/dist/_internal/components/data-display/DataViews/data-views-category-pill.js +15 -15
  11. package/dist/_internal/components/data-display/DataViews/data-views-columns.js +5 -5
  12. package/dist/_internal/components/data-display/DataViews/data-views-display-panel.js +5 -5
  13. package/dist/_internal/components/data-display/DataViews/data-views-empty.js +4 -4
  14. package/dist/_internal/components/data-display/DataViews/data-views-export.js +4 -4
  15. package/dist/_internal/components/data-display/DataViews/data-views-filter-panel.js +16 -16
  16. package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +6 -6
  17. package/dist/_internal/components/data-display/DataViews/data-views-grid-helpers.js +4 -4
  18. package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +31 -31
  19. package/dist/_internal/components/data-display/DataViews/data-views-inline-bar.js +20 -20
  20. package/dist/_internal/components/data-display/DataViews/data-views-more-filters.js +4 -4
  21. package/dist/_internal/components/data-display/DataViews/data-views-select-all-strip.js +4 -4
  22. package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +9 -9
  23. package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +7 -7
  24. package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +8 -8
  25. package/dist/_internal/components/data-display/DataViews/data-views-view-nav.js +4 -4
  26. package/dist/_internal/components/data-display/DataViews/use-data-views-state.js +6 -6
  27. package/dist/_internal/components/feedback/ConfirmAction/ConfirmButton.js +4 -4
  28. package/dist/_internal/components/feedback/index.js +4 -4
  29. package/dist/_internal/components/form/Button/Button.js +3 -3
  30. package/dist/_internal/components/form/Button/Button.metrics.js +3 -1
  31. package/dist/_internal/components/form/Button/Button.styles.js +2 -2
  32. package/dist/_internal/components/form/CategorySelect/CategoryPanel.js +8 -8
  33. package/dist/_internal/components/form/CategorySelect/CategoryPanelFoot.js +2 -2
  34. package/dist/_internal/components/form/CategorySelect/CategoryPanelHead.js +2 -2
  35. package/dist/_internal/components/form/CategorySelect/CategoryPanelList.js +4 -4
  36. package/dist/_internal/components/form/CategorySelect/CategoryQuickActions.js +2 -2
  37. package/dist/_internal/components/form/CategorySelect/CategoryRows.js +7 -9
  38. package/dist/_internal/components/form/CategorySelect/CategorySelect.js +14 -14
  39. package/dist/_internal/components/form/CategorySelect/CategorySelect.styles.js +3 -1
  40. package/dist/_internal/components/form/CategorySelect/CategoryTrigger.js +2 -2
  41. package/dist/_internal/components/form/CategorySelect/category-keyboard.js +2 -2
  42. package/dist/_internal/components/form/CategorySelect/category-tree.js +11 -3
  43. package/dist/_internal/components/form/CategorySelect/useCategoryCommit.js +2 -2
  44. package/dist/_internal/components/form/CategorySelect/useCategoryKeyHandler.js +3 -3
  45. package/dist/_internal/components/form/CategorySelect/useCategorySelect.js +2 -2
  46. package/dist/_internal/components/form/index.js +3 -3
  47. package/dist/_internal/components/form/total-form/Fields/CategoryField.js +15 -15
  48. package/dist/_internal/components/form/total-form/Fields/index.js +16 -16
  49. package/dist/_internal/components/form/total-form/SubmitButton.js +4 -4
  50. package/dist/_internal/components/index.js +5 -5
  51. package/dist/_internal/components/navigation/AppHeader/AppHeader.details.js +4 -4
  52. package/dist/_internal/components/navigation/index.js +4 -4
  53. package/dist/_internal/social-login-button/social-login-button.js +4 -4
  54. package/dist/_internal/user-avatar/user-avatar.js +4 -4
  55. package/dist/{chunk-6JGPPTLX.js → chunk-2GI7ANEL.js} +3 -3
  56. package/dist/{chunk-U2ZDN2AL.js → chunk-2PBGVMM2.js} +2 -2
  57. package/dist/{chunk-ITGG5OKP.js → chunk-5LQ7PKMW.js} +3 -1
  58. package/dist/chunk-5LQ7PKMW.js.map +1 -0
  59. package/dist/{chunk-4T7TXRGM.js → chunk-6ZY5XU4J.js} +3 -3
  60. package/dist/{chunk-CHPZTF34.js → chunk-7KWBGMQC.js} +2 -2
  61. package/dist/{chunk-6NSIZ2ZO.js → chunk-7MN6I5NK.js} +3 -3
  62. package/dist/{chunk-PN5CQKA2.js → chunk-AXTLFK4T.js} +2 -2
  63. package/dist/{chunk-JT6TERQL.js → chunk-BB2OUERT.js} +2 -2
  64. package/dist/{chunk-XUJG2HFW.js → chunk-BH5BXLGI.js} +2 -2
  65. package/dist/{chunk-4TXIZ5WI.js → chunk-CJUOBTLL.js} +2 -2
  66. package/dist/{chunk-NWNSUHEB.js → chunk-CT5K725B.js} +2 -2
  67. package/dist/{chunk-6OORHAE7.js → chunk-EQ7PWGBN.js} +3 -3
  68. package/dist/{chunk-EJQW2IYW.js → chunk-FPFUXRD2.js} +2 -2
  69. package/dist/{chunk-ZRPGVHTR.js → chunk-GGI7GM5O.js} +2 -2
  70. package/dist/{chunk-KIHQE5YA.js → chunk-GVDJQMFI.js} +2 -2
  71. package/dist/{chunk-TX5V42AY.js → chunk-HEBQS7QP.js} +3 -3
  72. package/dist/{chunk-BUQ2JUND.js → chunk-JO6BBSGY.js} +2 -2
  73. package/dist/{chunk-4ZV7N5CU.js → chunk-JXPHU3K5.js} +4 -4
  74. package/dist/{chunk-TW3BXVEU.js → chunk-KMXAPYU2.js} +2 -2
  75. package/dist/{chunk-CJBBGMIA.js → chunk-MNXVXW4T.js} +2 -2
  76. package/dist/{chunk-OFOYMZRY.js → chunk-NPN7JTRM.js} +22 -47
  77. package/dist/chunk-NPN7JTRM.js.map +1 -0
  78. package/dist/{chunk-BPLFM2GL.js → chunk-NYEA7JAI.js} +3 -3
  79. package/dist/{chunk-O6AZDQJJ.js → chunk-OE2ZVL67.js} +2 -2
  80. package/dist/{chunk-4523RYE7.js → chunk-OG2FP6SE.js} +2 -2
  81. package/dist/{chunk-XKV5ZIBG.js → chunk-OGGPQVIG.js} +2 -2
  82. package/dist/{chunk-SQNW22ZZ.js → chunk-OV4WREMW.js} +3 -3
  83. package/dist/{chunk-OVLNF4AZ.js → chunk-P7IVAMAU.js} +24 -35
  84. package/dist/chunk-P7IVAMAU.js.map +1 -0
  85. package/dist/{chunk-6LGWJFQK.js → chunk-PFDM2OIP.js} +2 -2
  86. package/dist/chunk-PN4HVM43.js +160 -0
  87. package/dist/chunk-PN4HVM43.js.map +1 -0
  88. package/dist/{chunk-WWM2YTW4.js → chunk-R2AZOWEZ.js} +21 -27
  89. package/dist/chunk-R2AZOWEZ.js.map +1 -0
  90. package/dist/{chunk-2GB5CIN6.js → chunk-RZVUY2MO.js} +8 -4
  91. package/dist/chunk-RZVUY2MO.js.map +1 -0
  92. package/dist/{chunk-YTOYMJPM.js → chunk-SLVQ33AN.js} +15 -5
  93. package/dist/chunk-SLVQ33AN.js.map +1 -0
  94. package/dist/{chunk-H5VEJDEK.js → chunk-TVPPA4RD.js} +7 -7
  95. package/dist/{chunk-ISH42UC7.js → chunk-USJPZ5Q7.js} +2 -2
  96. package/dist/{chunk-5OIH36KX.js → chunk-V4FKHLEB.js} +2 -2
  97. package/dist/{chunk-ZUIJ3N7N.js → chunk-V6Z4XS3I.js} +5 -3
  98. package/dist/chunk-V6Z4XS3I.js.map +1 -0
  99. package/dist/{chunk-JM64OEU6.js → chunk-WAPJ2DL3.js} +10 -13
  100. package/dist/chunk-WAPJ2DL3.js.map +1 -0
  101. package/dist/{chunk-QPLWDSYG.js → chunk-WM6Y2S6W.js} +4 -4
  102. package/dist/{chunk-EMMPNB6D.js → chunk-X4Z62ZQY.js} +2 -2
  103. package/dist/{chunk-ZAB65STX.js → chunk-XHHR6G35.js} +2 -2
  104. package/dist/{chunk-OP65D4UR.js → chunk-XR6WBW23.js} +2 -2
  105. package/dist/{chunk-WIT3Y7JT.js → chunk-Y2B3I3IH.js} +2 -2
  106. package/dist/{chunk-R6SNYC3D.js → chunk-Y3KRUT7O.js} +2 -2
  107. package/dist/{chunk-TPLKJHNN.js → chunk-ZC7RQDZ5.js} +8 -7
  108. package/dist/chunk-ZC7RQDZ5.js.map +1 -0
  109. package/dist/{chunk-TFO6GBW7.js → chunk-ZS44PY7T.js} +5 -1
  110. package/dist/chunk-ZS44PY7T.js.map +1 -0
  111. package/dist/{chunk-AJ6XEO4E.js → chunk-ZUY65MFH.js} +2 -2
  112. package/dist/{chunk-4X4KM4ZB.js → chunk-ZZMZF7PZ.js} +2 -2
  113. package/dist/data-display/CardKit.js +5 -5
  114. package/dist/data-display/DataViews.js +40 -40
  115. package/dist/feedback/ConfirmAction.js +4 -4
  116. package/dist/form/Button.js +3 -3
  117. package/dist/form/CategorySelect.js +24 -16
  118. package/dist/form/total-form.js +20 -20
  119. package/dist/native/chunks/{chunk-ZAQ3AZJM.js → chunk-2R2R35C5.js} +2 -2
  120. package/dist/native/chunks/{chunk-ATS7STZJ.js → chunk-WL5LXVCK.js} +1 -1
  121. package/dist/native/chunks/{chunk-ATS7STZJ.js.map → chunk-WL5LXVCK.js.map} +1 -1
  122. package/dist/native/data-display/EmptyState.js +2 -2
  123. package/dist/native/data-display/ErrorState.js +1 -1
  124. package/dist/native/data-display/FleetMap.js +2 -2
  125. package/dist/native/form/Button.js +1 -1
  126. package/dist/navigation/AppHeader.js +4 -4
  127. package/dist/social-login-button.js +4 -4
  128. package/dist/types/components/form/Button/Button.d.ts +7 -3
  129. package/dist/types/components/form/Button/Button.d.ts.map +1 -1
  130. package/dist/types/components/form/Button/Button.metrics.d.ts +25 -0
  131. package/dist/types/components/form/Button/Button.metrics.d.ts.map +1 -1
  132. package/dist/types/components/form/Button/Button.types.d.ts +37 -4
  133. package/dist/types/components/form/Button/Button.types.d.ts.map +1 -1
  134. package/dist/types/components/form/CategorySelect/CategoryPanel.d.ts.map +1 -1
  135. package/dist/types/components/form/CategorySelect/CategoryPanelList.d.ts +6 -0
  136. package/dist/types/components/form/CategorySelect/CategoryPanelList.d.ts.map +1 -1
  137. package/dist/types/components/form/CategorySelect/CategoryQuickActions.d.ts.map +1 -1
  138. package/dist/types/components/form/CategorySelect/CategoryRows.d.ts +12 -24
  139. package/dist/types/components/form/CategorySelect/CategoryRows.d.ts.map +1 -1
  140. package/dist/types/components/form/CategorySelect/CategorySelect.d.ts +10 -7
  141. package/dist/types/components/form/CategorySelect/CategorySelect.d.ts.map +1 -1
  142. package/dist/types/components/form/CategorySelect/CategorySelect.styles.d.ts +11 -0
  143. package/dist/types/components/form/CategorySelect/CategorySelect.styles.d.ts.map +1 -1
  144. package/dist/types/components/form/CategorySelect/CategorySelect.types.d.ts +10 -2
  145. package/dist/types/components/form/CategorySelect/CategorySelect.types.d.ts.map +1 -1
  146. package/dist/types/components/form/CategorySelect/category-keyboard.d.ts.map +1 -1
  147. package/dist/types/components/form/CategorySelect/category-tree.d.ts +52 -20
  148. package/dist/types/components/form/CategorySelect/category-tree.d.ts.map +1 -1
  149. package/dist/types/components/form/CategorySelect/index.d.ts +1 -1
  150. package/dist/types/components/form/CategorySelect/index.d.ts.map +1 -1
  151. package/dist/types/components/form/CategorySelect/useCategorySelect.d.ts +2 -0
  152. package/dist/types/components/form/CategorySelect/useCategorySelect.d.ts.map +1 -1
  153. package/dist/types-native/components/form/Button/Button.metrics.d.ts +25 -0
  154. package/dist/types-native/components/form/Button/Button.metrics.d.ts.map +1 -1
  155. package/dist/user-avatar.js +4 -4
  156. package/package.json +1 -1
  157. package/src/components/form/Button/Button.md +39 -0
  158. package/src/components/form/Button/Button.metrics.ts +26 -0
  159. package/src/components/form/Button/Button.tsx +77 -4
  160. package/src/components/form/Button/Button.types.ts +47 -5
  161. package/src/components/form/CategorySelect/CategoryPanel.tsx +1 -0
  162. package/src/components/form/CategorySelect/CategoryPanelList.tsx +42 -34
  163. package/src/components/form/CategorySelect/CategoryQuickActions.tsx +6 -4
  164. package/src/components/form/CategorySelect/CategoryRows.tsx +45 -81
  165. package/src/components/form/CategorySelect/CategorySelect.md +47 -13
  166. package/src/components/form/CategorySelect/CategorySelect.styles.ts +13 -0
  167. package/src/components/form/CategorySelect/CategorySelect.tsx +16 -14
  168. package/src/components/form/CategorySelect/CategorySelect.types.ts +10 -2
  169. package/src/components/form/CategorySelect/category-keyboard.ts +27 -31
  170. package/src/components/form/CategorySelect/category-tree.ts +143 -63
  171. package/src/components/form/CategorySelect/index.ts +4 -0
  172. package/src/components/form/CategorySelect/useCategorySelect.ts +15 -1
  173. package/dist/chunk-2GB5CIN6.js.map +0 -1
  174. package/dist/chunk-ITGG5OKP.js.map +0 -1
  175. package/dist/chunk-JM64OEU6.js.map +0 -1
  176. package/dist/chunk-LR6MSKTT.js +0 -124
  177. package/dist/chunk-LR6MSKTT.js.map +0 -1
  178. package/dist/chunk-OFOYMZRY.js.map +0 -1
  179. package/dist/chunk-OVLNF4AZ.js.map +0 -1
  180. package/dist/chunk-TFO6GBW7.js.map +0 -1
  181. package/dist/chunk-TPLKJHNN.js.map +0 -1
  182. package/dist/chunk-WWM2YTW4.js.map +0 -1
  183. package/dist/chunk-YTOYMJPM.js.map +0 -1
  184. package/dist/chunk-ZUIJ3N7N.js.map +0 -1
  185. /package/dist/{chunk-6JGPPTLX.js.map → chunk-2GI7ANEL.js.map} +0 -0
  186. /package/dist/{chunk-U2ZDN2AL.js.map → chunk-2PBGVMM2.js.map} +0 -0
  187. /package/dist/{chunk-4T7TXRGM.js.map → chunk-6ZY5XU4J.js.map} +0 -0
  188. /package/dist/{chunk-CHPZTF34.js.map → chunk-7KWBGMQC.js.map} +0 -0
  189. /package/dist/{chunk-6NSIZ2ZO.js.map → chunk-7MN6I5NK.js.map} +0 -0
  190. /package/dist/{chunk-PN5CQKA2.js.map → chunk-AXTLFK4T.js.map} +0 -0
  191. /package/dist/{chunk-JT6TERQL.js.map → chunk-BB2OUERT.js.map} +0 -0
  192. /package/dist/{chunk-XUJG2HFW.js.map → chunk-BH5BXLGI.js.map} +0 -0
  193. /package/dist/{chunk-4TXIZ5WI.js.map → chunk-CJUOBTLL.js.map} +0 -0
  194. /package/dist/{chunk-NWNSUHEB.js.map → chunk-CT5K725B.js.map} +0 -0
  195. /package/dist/{chunk-6OORHAE7.js.map → chunk-EQ7PWGBN.js.map} +0 -0
  196. /package/dist/{chunk-EJQW2IYW.js.map → chunk-FPFUXRD2.js.map} +0 -0
  197. /package/dist/{chunk-ZRPGVHTR.js.map → chunk-GGI7GM5O.js.map} +0 -0
  198. /package/dist/{chunk-KIHQE5YA.js.map → chunk-GVDJQMFI.js.map} +0 -0
  199. /package/dist/{chunk-TX5V42AY.js.map → chunk-HEBQS7QP.js.map} +0 -0
  200. /package/dist/{chunk-BUQ2JUND.js.map → chunk-JO6BBSGY.js.map} +0 -0
  201. /package/dist/{chunk-4ZV7N5CU.js.map → chunk-JXPHU3K5.js.map} +0 -0
  202. /package/dist/{chunk-TW3BXVEU.js.map → chunk-KMXAPYU2.js.map} +0 -0
  203. /package/dist/{chunk-CJBBGMIA.js.map → chunk-MNXVXW4T.js.map} +0 -0
  204. /package/dist/{chunk-BPLFM2GL.js.map → chunk-NYEA7JAI.js.map} +0 -0
  205. /package/dist/{chunk-O6AZDQJJ.js.map → chunk-OE2ZVL67.js.map} +0 -0
  206. /package/dist/{chunk-4523RYE7.js.map → chunk-OG2FP6SE.js.map} +0 -0
  207. /package/dist/{chunk-XKV5ZIBG.js.map → chunk-OGGPQVIG.js.map} +0 -0
  208. /package/dist/{chunk-SQNW22ZZ.js.map → chunk-OV4WREMW.js.map} +0 -0
  209. /package/dist/{chunk-6LGWJFQK.js.map → chunk-PFDM2OIP.js.map} +0 -0
  210. /package/dist/{chunk-H5VEJDEK.js.map → chunk-TVPPA4RD.js.map} +0 -0
  211. /package/dist/{chunk-ISH42UC7.js.map → chunk-USJPZ5Q7.js.map} +0 -0
  212. /package/dist/{chunk-5OIH36KX.js.map → chunk-V4FKHLEB.js.map} +0 -0
  213. /package/dist/{chunk-QPLWDSYG.js.map → chunk-WM6Y2S6W.js.map} +0 -0
  214. /package/dist/{chunk-EMMPNB6D.js.map → chunk-X4Z62ZQY.js.map} +0 -0
  215. /package/dist/{chunk-ZAB65STX.js.map → chunk-XHHR6G35.js.map} +0 -0
  216. /package/dist/{chunk-OP65D4UR.js.map → chunk-XR6WBW23.js.map} +0 -0
  217. /package/dist/{chunk-WIT3Y7JT.js.map → chunk-Y2B3I3IH.js.map} +0 -0
  218. /package/dist/{chunk-R6SNYC3D.js.map → chunk-Y3KRUT7O.js.map} +0 -0
  219. /package/dist/{chunk-AJ6XEO4E.js.map → chunk-ZUY65MFH.js.map} +0 -0
  220. /package/dist/{chunk-4X4KM4ZB.js.map → chunk-ZZMZF7PZ.js.map} +0 -0
  221. /package/dist/native/chunks/{chunk-ZAQ3AZJM.js.map → chunk-2R2R35C5.js.map} +0 -0
@@ -1,4 +1,4 @@
1
- import { isLeafCategory, type CategoryRowRef } from './category-tree';
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.find((group) => group.category.id === id);
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 category. On a subcategory it is a no-op: there is nothing deeper. */
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?.kind === 'category') context.expand(row.id);
46
+ if (row?.branch) context.expand(row.id);
47
47
  }
48
48
 
49
- /** ← collapses a category, or walks a subcategory back up to its parent. */
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.kind === 'category') {
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 category row does when it is activated — the one answer the click, the
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 category that is only a HEADING expands instead of marking: the row has no
71
- * control, so "mark" has nothing to mean there. A childless category is not a
72
- * heading — it is the leaf — so it marks.
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 activateCategoryRow(context: CategoryKeyContext, categoryId: string): void {
75
- const group = groupOf(context, categoryId);
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.toggleCategory(group);
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 (!row) return;
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 (!row) return;
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(/[\u0300-\u036f]/g, '')
18
+ .replace(/[̀-ͯ]/g, '')
19
19
  .toLowerCase();
20
20
  }
21
21
 
22
22
  /**
23
- * Group a flat `parentId` list into top level + children, preserving input order.
23
+ * Whether following `parentId` up from `startId` ever comes back to a node it
24
+ * already passed.
24
25
  *
25
- * An option whose `parentId` names no top-level row is promoted to top level
26
- * rather than dropped — a category is never invisible just because its parent
27
- * was filtered out of the payload, or because the tree is deeper than two levels.
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
- export function buildCategoryGroups(options: CategorySelectOption[]): CategoryGroup[] {
30
- const roots = options.filter((option) => !option.parentId);
31
- const rootIds = new Set(roots.map((root) => root.id));
32
- const groups = new Map<string, CategoryGroup>(
33
- roots.map((root) => [root.id, { category: root, subcategories: [] }]),
34
- );
35
- const orphans: CategoryGroup[] = [];
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 (!option.parentId) return;
39
- const parent = groups.get(option.parentId);
40
- if (parent) {
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
- return [...groups.values(), ...orphans];
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 category IS the leaf, because nothing sits under it.
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 all groups, in display order. */
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
- /** The leaves a category stands for — its subcategories, or itself when childless. */
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
- return isLeafCategory(group) ? [group.category.id] : group.subcategories.map((sub) => sub.id);
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 category that is fully
109
- * selected becomes ONE chip bearing the category's name, rather than one chip
110
- * per subcategory — "Bebidas" instead of six drinks.
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
- return [{ id: group.category.id, label: group.category.name, whole: true }];
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 `whole` chip removes the whole category;
130
- * any other chip removes just that leaf.
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.find((candidate) => candidate.category.id === chipId);
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 across BOTH levels, keeping every hit under its parent.
230
+ * Search every level, keeping each hit under its parents.
149
231
  *
150
- * A category that matches keeps all of its subcategories, so "Bebidas" shows the
151
- * whole group. A category that does not match keeps only the subcategories that
152
- * do — the hit still arrives with its parent visible above it, which is the
153
- * context that tells "Massas" (mercearia) from "Massas" (pratos principais).
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
- return groups.flatMap((group) => {
159
- if (foldText(group.category.name).includes(needle)) return [group];
160
- const subcategories = group.subcategories.filter((sub) =>
161
- foldText(sub.name).includes(needle),
162
- );
163
- return subcategories.length > 0 ? [{ category: group.category, subcategories }] : [];
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
- /** Owning category id — lets ← jump from a subcategory back to its parent. */
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 categories are expanded. */
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
- return groups.flatMap((group) => {
206
- const head: CategoryRowRef = { kind: 'category', id: group.category.id };
207
- if (!isExpanded(group.category.id)) return [head];
208
- return [
209
- head,
210
- ...group.subcategories.map(
211
- (sub): CategoryRowRef => ({
212
- kind: 'subcategory',
213
- id: sub.id,
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.map((g) => g.category.id)) : new Set());
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"]}