@12-apps/ui 6.12.1 → 6.13.1

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 (137) hide show
  1. package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +25 -25
  2. package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +27 -27
  3. package/dist/_internal/components/data-display/DataViews/base-card.js +2 -2
  4. package/dist/_internal/components/data-display/DataViews/base-list-card-geometry.js +5 -5
  5. package/dist/_internal/components/data-display/DataViews/base-list-card-slots.js +4 -4
  6. package/dist/_internal/components/data-display/DataViews/base-list-card.js +6 -6
  7. package/dist/_internal/components/data-display/DataViews/data-views-category-pill.js +11 -11
  8. package/dist/_internal/components/data-display/DataViews/data-views-display-panel.js +4 -4
  9. package/dist/_internal/components/data-display/DataViews/data-views-display-tabs.js +3 -3
  10. package/dist/_internal/components/data-display/DataViews/data-views-filter-panel.js +12 -12
  11. package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +4 -4
  12. package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +23 -23
  13. package/dist/_internal/components/data-display/DataViews/data-views-inline-bar.js +12 -12
  14. package/dist/_internal/components/data-display/DataViews/data-views-layout-context.js +2 -2
  15. package/dist/_internal/components/data-display/DataViews/data-views-scope-tabs.js +3 -3
  16. package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +6 -6
  17. package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +5 -5
  18. package/dist/_internal/components/data-display/DataViews/data-views-types.js +1 -1
  19. package/dist/_internal/components/data-display/DataViews/list-card-rails.js +3 -3
  20. package/dist/_internal/components/data-display/DataViews/use-data-views-state.js +2 -2
  21. package/dist/_internal/components/data-display/DataViews/use-saved-views-controller.js +2 -2
  22. package/dist/_internal/components/form/CategorySelect/CategoryPanel.js +5 -5
  23. package/dist/_internal/components/form/CategorySelect/CategoryPanelList.js +3 -3
  24. package/dist/_internal/components/form/CategorySelect/CategoryQuickActions.js +2 -2
  25. package/dist/_internal/components/form/CategorySelect/CategoryRows.js +2 -2
  26. package/dist/_internal/components/form/CategorySelect/CategorySelect.js +10 -10
  27. package/dist/_internal/components/form/CategorySelect/category-keyboard.js +2 -1
  28. package/dist/_internal/components/form/CategorySelect/category-tree.js +3 -1
  29. package/dist/_internal/components/form/CategorySelect/useCategoryCommit.js +2 -1
  30. package/dist/_internal/components/form/CategorySelect/useCategoryKeyHandler.js +3 -2
  31. package/dist/_internal/components/form/CategorySelect/useCategorySelect.js +2 -2
  32. package/dist/_internal/components/form/total-form/Fields/CategoryField.js +11 -11
  33. package/dist/_internal/components/form/total-form/Fields/index.js +12 -12
  34. package/dist/{chunk-HQBXB5QB.js → chunk-2GB5CIN6.js} +2 -2
  35. package/dist/{chunk-6UGRH32J.js → chunk-4KSOSI3U.js} +4 -4
  36. package/dist/{chunk-IKBQ7CS2.js → chunk-56SMIHWE.js} +2 -2
  37. package/dist/{chunk-LWYXMCX6.js → chunk-5ZZPQ3MT.js} +4 -4
  38. package/dist/{chunk-ZBYWGL5G.js → chunk-6U7NA74A.js} +2 -2
  39. package/dist/{chunk-XQZOMXP3.js → chunk-AWTERXCW.js} +2 -2
  40. package/dist/{chunk-JIEEUHNS.js → chunk-AZSFIQIA.js} +8 -8
  41. package/dist/{chunk-TY6CTYCF.js → chunk-BV5XTGKB.js} +3 -3
  42. package/dist/chunk-BV5XTGKB.js.map +1 -0
  43. package/dist/{chunk-5F4NIRVF.js → chunk-CKZFYGBY.js} +10 -6
  44. package/dist/chunk-CKZFYGBY.js.map +1 -0
  45. package/dist/{chunk-T4JGNZAR.js → chunk-CZPU5TW3.js} +2 -2
  46. package/dist/{chunk-S6T722WH.js → chunk-DEOC5OI7.js} +3 -3
  47. package/dist/{chunk-KWUQZDPL.js → chunk-ECIGNYSI.js} +3 -2
  48. package/dist/chunk-ECIGNYSI.js.map +1 -0
  49. package/dist/{chunk-Q5GXVDXL.js → chunk-EICVL6GS.js} +2 -2
  50. package/dist/{chunk-USBTXEBG.js → chunk-HGBZZDP6.js} +2 -2
  51. package/dist/{chunk-6IAQGSXD.js → chunk-HR7YTBR7.js} +2 -2
  52. package/dist/{chunk-NXTKZSMT.js → chunk-IN642YHK.js} +2 -2
  53. package/dist/{chunk-QBJVRPTF.js → chunk-ISH42UC7.js} +2 -2
  54. package/dist/{chunk-AGKQBS4C.js → chunk-JKLWHENB.js} +3 -3
  55. package/dist/{chunk-SVXSBLVD.js → chunk-KEDI6IRA.js} +2 -2
  56. package/dist/{chunk-C6ATAHBX.js → chunk-LHHAYZ55.js} +1 -1
  57. package/dist/chunk-LHHAYZ55.js.map +1 -0
  58. package/dist/{chunk-Z5E2PXGN.js → chunk-LPYB4Y7C.js} +49 -16
  59. package/dist/chunk-LPYB4Y7C.js.map +1 -0
  60. package/dist/{chunk-DVTCZR3K.js → chunk-LR6MSKTT.js} +7 -5
  61. package/dist/chunk-LR6MSKTT.js.map +1 -0
  62. package/dist/{chunk-CY5X3HNQ.js → chunk-OGGHD64H.js} +2 -2
  63. package/dist/{chunk-QFHUWD3C.js → chunk-QQE2PVLO.js} +3 -3
  64. package/dist/{chunk-N7SBWD2V.js → chunk-RDYBS45D.js} +4 -4
  65. package/dist/{chunk-F6FT2LIR.js → chunk-SCGVB7DS.js} +2 -2
  66. package/dist/{chunk-TDSRBCBG.js → chunk-T4BFDZGU.js} +9 -8
  67. package/dist/chunk-T4BFDZGU.js.map +1 -0
  68. package/dist/{chunk-T6V3DKJ5.js → chunk-WLCVOQCJ.js} +2 -2
  69. package/dist/{chunk-GCC66ZSD.js → chunk-WWM2YTW4.js} +29 -13
  70. package/dist/chunk-WWM2YTW4.js.map +1 -0
  71. package/dist/{chunk-GQTBWJYK.js → chunk-WXBBWSNU.js} +2 -2
  72. package/dist/{chunk-VN6Z6F7R.js → chunk-ZAB65STX.js} +11 -6
  73. package/dist/chunk-ZAB65STX.js.map +1 -0
  74. package/dist/{chunk-HVIB6WMD.js → chunk-ZPIMPOHV.js} +2 -2
  75. package/dist/{chunk-E3IPN3KN.js → chunk-ZUIJ3N7N.js} +2 -2
  76. package/dist/data-display/DataViews.js +31 -31
  77. package/dist/form/CategorySelect.js +10 -10
  78. package/dist/form/total-form.js +12 -12
  79. package/dist/types/components/data-display/DataViews/data-views-category-pill.d.ts +6 -0
  80. package/dist/types/components/data-display/DataViews/data-views-category-pill.d.ts.map +1 -1
  81. package/dist/types/components/data-display/DataViews/data-views-grid-parts.d.ts.map +1 -1
  82. package/dist/types/components/data-display/DataViews/data-views-types.d.ts +21 -0
  83. package/dist/types/components/data-display/DataViews/data-views-types.d.ts.map +1 -1
  84. package/dist/types/components/data-display/DataViews/use-data-views-state.d.ts +10 -2
  85. package/dist/types/components/data-display/DataViews/use-data-views-state.d.ts.map +1 -1
  86. package/dist/types/components/form/CategorySelect/CategoryPanelList.d.ts.map +1 -1
  87. package/dist/types/components/form/CategorySelect/CategoryRows.d.ts +8 -2
  88. package/dist/types/components/form/CategorySelect/CategoryRows.d.ts.map +1 -1
  89. package/dist/types/components/form/CategorySelect/category-keyboard.d.ts +1 -1
  90. package/dist/types/components/form/CategorySelect/category-keyboard.d.ts.map +1 -1
  91. package/dist/types/components/form/CategorySelect/category-tree.d.ts +12 -0
  92. package/dist/types/components/form/CategorySelect/category-tree.d.ts.map +1 -1
  93. package/dist/types/components/form/CategorySelect/useCategoryCommit.d.ts.map +1 -1
  94. package/package.json +1 -1
  95. package/src/components/data-display/DataViews/data-views-category-pill.tsx +7 -0
  96. package/src/components/data-display/DataViews/data-views-grid-parts.tsx +2 -1
  97. package/src/components/data-display/DataViews/data-views-types.ts +24 -0
  98. package/src/components/data-display/DataViews/use-data-views-state.ts +13 -3
  99. package/src/components/form/CategorySelect/CategoryPanelList.tsx +9 -5
  100. package/src/components/form/CategorySelect/CategoryRows.tsx +64 -23
  101. package/src/components/form/CategorySelect/CategorySelect.md +33 -6
  102. package/src/components/form/CategorySelect/category-keyboard.ts +34 -15
  103. package/src/components/form/CategorySelect/category-tree.ts +17 -8
  104. package/src/components/form/CategorySelect/useCategoryCommit.ts +10 -6
  105. package/dist/chunk-5F4NIRVF.js.map +0 -1
  106. package/dist/chunk-C6ATAHBX.js.map +0 -1
  107. package/dist/chunk-DVTCZR3K.js.map +0 -1
  108. package/dist/chunk-GCC66ZSD.js.map +0 -1
  109. package/dist/chunk-KWUQZDPL.js.map +0 -1
  110. package/dist/chunk-TDSRBCBG.js.map +0 -1
  111. package/dist/chunk-TY6CTYCF.js.map +0 -1
  112. package/dist/chunk-VN6Z6F7R.js.map +0 -1
  113. package/dist/chunk-Z5E2PXGN.js.map +0 -1
  114. /package/dist/{chunk-HQBXB5QB.js.map → chunk-2GB5CIN6.js.map} +0 -0
  115. /package/dist/{chunk-6UGRH32J.js.map → chunk-4KSOSI3U.js.map} +0 -0
  116. /package/dist/{chunk-IKBQ7CS2.js.map → chunk-56SMIHWE.js.map} +0 -0
  117. /package/dist/{chunk-LWYXMCX6.js.map → chunk-5ZZPQ3MT.js.map} +0 -0
  118. /package/dist/{chunk-ZBYWGL5G.js.map → chunk-6U7NA74A.js.map} +0 -0
  119. /package/dist/{chunk-XQZOMXP3.js.map → chunk-AWTERXCW.js.map} +0 -0
  120. /package/dist/{chunk-JIEEUHNS.js.map → chunk-AZSFIQIA.js.map} +0 -0
  121. /package/dist/{chunk-T4JGNZAR.js.map → chunk-CZPU5TW3.js.map} +0 -0
  122. /package/dist/{chunk-S6T722WH.js.map → chunk-DEOC5OI7.js.map} +0 -0
  123. /package/dist/{chunk-Q5GXVDXL.js.map → chunk-EICVL6GS.js.map} +0 -0
  124. /package/dist/{chunk-USBTXEBG.js.map → chunk-HGBZZDP6.js.map} +0 -0
  125. /package/dist/{chunk-6IAQGSXD.js.map → chunk-HR7YTBR7.js.map} +0 -0
  126. /package/dist/{chunk-NXTKZSMT.js.map → chunk-IN642YHK.js.map} +0 -0
  127. /package/dist/{chunk-QBJVRPTF.js.map → chunk-ISH42UC7.js.map} +0 -0
  128. /package/dist/{chunk-AGKQBS4C.js.map → chunk-JKLWHENB.js.map} +0 -0
  129. /package/dist/{chunk-SVXSBLVD.js.map → chunk-KEDI6IRA.js.map} +0 -0
  130. /package/dist/{chunk-CY5X3HNQ.js.map → chunk-OGGHD64H.js.map} +0 -0
  131. /package/dist/{chunk-QFHUWD3C.js.map → chunk-QQE2PVLO.js.map} +0 -0
  132. /package/dist/{chunk-N7SBWD2V.js.map → chunk-RDYBS45D.js.map} +0 -0
  133. /package/dist/{chunk-F6FT2LIR.js.map → chunk-SCGVB7DS.js.map} +0 -0
  134. /package/dist/{chunk-T6V3DKJ5.js.map → chunk-WLCVOQCJ.js.map} +0 -0
  135. /package/dist/{chunk-GQTBWJYK.js.map → chunk-WXBBWSNU.js.map} +0 -0
  136. /package/dist/{chunk-HVIB6WMD.js.map → chunk-ZPIMPOHV.js.map} +0 -0
  137. /package/dist/{chunk-E3IPN3KN.js.map → chunk-ZUIJ3N7N.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/form/CategorySelect/category-keyboard.ts"],"sourcesContent":["import type { CategoryRowRef } from './category-tree';\nimport type { CategoryGroup } from './CategorySelect.types';\n\n/** What a key handler is allowed to do to the panel. */\nexport interface CategoryKeyContext {\n rows: CategoryRowRef[];\n activeIndex: number;\n groups: CategoryGroup[];\n /** Single-select mode picks on Enter/Space instead of toggling a draft. */\n single: boolean;\n /** Categories are headings, not checkboxes, unless parents are selectable. */\n allowParentSelection: boolean;\n setActiveIndex: (next: number) => void;\n toggleExpanded: (categoryId: string) => void;\n expand: (categoryId: string) => void;\n collapse: (categoryId: string) => void;\n toggleCategory: (group: CategoryGroup) => void;\n toggleSubcategory: (id: string) => void;\n commit: () => void;\n cancel: () => void;\n pick: (id: string) => void;\n}\n\n/** The row under the keyboard cursor, if the cursor is on one. */\nfunction activeRow(context: CategoryKeyContext): CategoryRowRef | undefined {\n return context.activeIndex >= 0 ? context.rows[context.activeIndex] : undefined;\n}\n\nfunction groupOf(context: CategoryKeyContext, id: string): CategoryGroup | undefined {\n return context.groups.find((group) => group.category.id === id);\n}\n\nfunction moveCursor(context: CategoryKeyContext, delta: number): void {\n const { rows, activeIndex } = context;\n if (rows.length === 0) return;\n const next =\n delta > 0\n ? Math.min(rows.length - 1, activeIndex + 1)\n : Math.max(0, activeIndex - 1);\n context.setActiveIndex(next);\n}\n\n/** → opens a category. On a subcategory it is a no-op: there is nothing deeper. */\nfunction handleRight(context: CategoryKeyContext): void {\n const row = activeRow(context);\n if (row?.kind === 'category') context.expand(row.id);\n}\n\n/** ← collapses a category, or walks a subcategory back up to its parent. */\nfunction handleLeft(context: CategoryKeyContext): void {\n const row = activeRow(context);\n if (!row) return;\n if (row.kind === 'category') {\n context.collapse(row.id);\n return;\n }\n if (!row.parentId) return;\n context.collapse(row.parentId);\n const parentIndex = context.rows.findIndex(\n (candidate) => candidate.kind === 'category' && candidate.id === row.parentId,\n );\n if (parentIndex >= 0) context.setActiveIndex(parentIndex);\n}\n\n/**\n * Space marks. On a category that is only a HEADING it expands instead — the\n * row has no checkbox, so \"mark\" has nothing to mean there.\n */\nfunction handleSpace(context: CategoryKeyContext): void {\n const row = activeRow(context);\n if (!row) return;\n if (context.single) {\n context.pick(row.id);\n return;\n }\n if (row.kind === 'subcategory') {\n context.toggleSubcategory(row.id);\n return;\n }\n const group = groupOf(context, row.id);\n if (!group) return;\n if (context.allowParentSelection) context.toggleCategory(group);\n else context.toggleExpanded(row.id);\n}\n\nfunction handleEnter(context: CategoryKeyContext): void {\n const row = activeRow(context);\n if (context.single) {\n if (row) context.pick(row.id);\n return;\n }\n context.commit();\n}\n\n/** Key → behaviour. A map keeps this dispatch flat instead of a long if-chain. */\nconst HANDLERS: Record<string, (context: CategoryKeyContext) => void> = {\n ArrowDown: (context) => moveCursor(context, 1),\n ArrowUp: (context) => moveCursor(context, -1),\n ArrowRight: handleRight,\n ArrowLeft: handleLeft,\n ' ': handleSpace,\n Enter: handleEnter,\n Escape: (context) => context.cancel(),\n};\n\n/**\n * Keys the SEARCH BOX has a claim on: Space types a space, and the horizontal\n * arrows move the caret.\n *\n * They are only yielded while the field actually holds text. The panel opens\n * with focus in an EMPTY search box, so yielding unconditionally — as the\n * prototype does — would make Space and ←/→ dead on arrival for anyone driving\n * the tree from the keyboard, which is most of the point of having it. With no\n * text there is no caret to move and no word to space, so the tree takes them;\n * type one character and they revert to the field.\n */\nconst TEXT_ENTRY_KEYS = new Set([' ', 'ArrowLeft', 'ArrowRight']);\n\n/**\n * Drive the tree from the keyboard.\n *\n * Returns `true` when the key was consumed, so the caller can `preventDefault`\n * only for keys this actually handled — an unhandled key must still reach the\n * search input untouched.\n */\nexport function handleCategoryKeyDown(\n key: string,\n editingSearchText: boolean,\n context: CategoryKeyContext,\n): boolean {\n if (editingSearchText && TEXT_ENTRY_KEYS.has(key)) return false;\n const handler = HANDLERS[key];\n if (!handler) return false;\n handler(context);\n return true;\n}\n"],"mappings":";AAwBA,SAAS,UAAU,SAAyD;AAC1E,SAAO,QAAQ,eAAe,IAAI,QAAQ,KAAK,QAAQ,WAAW,IAAI;AACxE;AAEA,SAAS,QAAQ,SAA6B,IAAuC;AACnF,SAAO,QAAQ,OAAO,KAAK,CAAC,UAAU,MAAM,SAAS,OAAO,EAAE;AAChE;AAEA,SAAS,WAAW,SAA6B,OAAqB;AACpE,QAAM,EAAE,MAAM,YAAY,IAAI;AAC9B,MAAI,KAAK,WAAW,EAAG;AACvB,QAAM,OACJ,QAAQ,IACJ,KAAK,IAAI,KAAK,SAAS,GAAG,cAAc,CAAC,IACzC,KAAK,IAAI,GAAG,cAAc,CAAC;AACjC,UAAQ,eAAe,IAAI;AAC7B;AAGA,SAAS,YAAY,SAAmC;AACtD,QAAM,MAAM,UAAU,OAAO;AAC7B,MAAI,KAAK,SAAS,WAAY,SAAQ,OAAO,IAAI,EAAE;AACrD;AAGA,SAAS,WAAW,SAAmC;AACrD,QAAM,MAAM,UAAU,OAAO;AAC7B,MAAI,CAAC,IAAK;AACV,MAAI,IAAI,SAAS,YAAY;AAC3B,YAAQ,SAAS,IAAI,EAAE;AACvB;AAAA,EACF;AACA,MAAI,CAAC,IAAI,SAAU;AACnB,UAAQ,SAAS,IAAI,QAAQ;AAC7B,QAAM,cAAc,QAAQ,KAAK;AAAA,IAC/B,CAAC,cAAc,UAAU,SAAS,cAAc,UAAU,OAAO,IAAI;AAAA,EACvE;AACA,MAAI,eAAe,EAAG,SAAQ,eAAe,WAAW;AAC1D;AAMA,SAAS,YAAY,SAAmC;AACtD,QAAM,MAAM,UAAU,OAAO;AAC7B,MAAI,CAAC,IAAK;AACV,MAAI,QAAQ,QAAQ;AAClB,YAAQ,KAAK,IAAI,EAAE;AACnB;AAAA,EACF;AACA,MAAI,IAAI,SAAS,eAAe;AAC9B,YAAQ,kBAAkB,IAAI,EAAE;AAChC;AAAA,EACF;AACA,QAAM,QAAQ,QAAQ,SAAS,IAAI,EAAE;AACrC,MAAI,CAAC,MAAO;AACZ,MAAI,QAAQ,qBAAsB,SAAQ,eAAe,KAAK;AAAA,MACzD,SAAQ,eAAe,IAAI,EAAE;AACpC;AAEA,SAAS,YAAY,SAAmC;AACtD,QAAM,MAAM,UAAU,OAAO;AAC7B,MAAI,QAAQ,QAAQ;AAClB,QAAI,IAAK,SAAQ,KAAK,IAAI,EAAE;AAC5B;AAAA,EACF;AACA,UAAQ,OAAO;AACjB;AAGA,IAAM,WAAkE;AAAA,EACtE,WAAW,CAAC,YAAY,WAAW,SAAS,CAAC;AAAA,EAC7C,SAAS,CAAC,YAAY,WAAW,SAAS,EAAE;AAAA,EAC5C,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,KAAK;AAAA,EACL,OAAO;AAAA,EACP,QAAQ,CAAC,YAAY,QAAQ,OAAO;AACtC;AAaA,IAAM,kBAAkB,oBAAI,IAAI,CAAC,KAAK,aAAa,YAAY,CAAC;AASzD,SAAS,sBACd,KACA,mBACA,SACS;AACT,MAAI,qBAAqB,gBAAgB,IAAI,GAAG,EAAG,QAAO;AAC1D,QAAM,UAAU,SAAS,GAAG;AAC5B,MAAI,CAAC,QAAS,QAAO;AACrB,UAAQ,OAAO;AACf,SAAO;AACT;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/DataViews/data-views-category-pill.tsx"],"sourcesContent":["\"use client\";\n\nimport { useDataViewsCopy } from \"./data-views-copy-context\";\nimport { CategorySelect } from \"../../form/CategorySelect\";\nimport { MultiSelectDropdown } from \"../../layout/ContentToolbar\";\n\nimport type { FilterFieldConfig, FilterOption } from \"./data-views-types\";\n\n/**\n * Diff a whole-array selection back into per-value toggles.\n *\n * The filter state's only channel is `onTogglePill(field, value, checked)`, but\n * a tree hands back the entire selection at once. Diffing here keeps that one\n * channel the single way a pill ever changes, rather than giving the\n * hierarchical control a private path into the state.\n */\nexport function emitPillDiff(\n fieldId: string,\n before: readonly string[],\n after: readonly string[],\n onTogglePill: (fieldId: string, value: string, checked: boolean) => void,\n): void {\n const had = new Set(before);\n const wants = new Set(after);\n after.forEach((value) => {\n if (!had.has(value)) onTogglePill(fieldId, value, true);\n });\n before.forEach((value) => {\n if (!wants.has(value)) onTogglePill(fieldId, value, false);\n });\n}\n\n/** A filter option as the category tree wants it. */\nconst toCategoryOption = (option: FilterOption) => ({\n id: option.value,\n name: option.label,\n parentId: option.parentId ?? null,\n});\n\ninterface PillControlProps<T extends Record<string, unknown>> {\n fieldId: string;\n pill: FilterFieldConfig<T>;\n selected: string[];\n onTogglePill: (fieldId: string, value: string, checked: boolean) => void;\n onClearField: (fieldId: string) => void;\n onOpenChange?: (open: boolean) => void;\n testIdPrefix: string;\n /** `stacked` in the filter panel, `pill` on the inline bar. */\n layout?: \"stacked\" | \"pill\";\n}\n\n/**\n * One filter pill: the hierarchical category tree, or the flat multi-select.\n *\n * A category set nests, and flattening it into one list is what made \"Massas\"\n * (mercearia) indistinguishable from \"Massas\" (pratos principais). `control:\n * \"category\"` opts a field into the tree, which shows each hit under its parent.\n */\nexport function PillControl<T extends Record<string, unknown>>({\n fieldId,\n pill,\n selected,\n onTogglePill,\n onClearField,\n onOpenChange,\n testIdPrefix,\n layout = \"pill\",\n}: PillControlProps<T>): React.JSX.Element {\n const copy = useDataViewsCopy();\n const testId = `${testIdPrefix}-filter-${fieldId}`;\n if (pill.control === \"category\") {\n return (\n <CategorySelect\n copy={copy.categorySelect}\n placeholder={pill.label}\n label={layout === \"stacked\" ? pill.label : undefined}\n fullWidth={layout === \"stacked\"}\n options={pill.options.map(toCategoryOption)}\n value={selected}\n onChange={(next) => emitPillDiff(fieldId, selected, next, onTogglePill)}\n dataTestId={testId}\n />\n );\n }\n return (\n <MultiSelectDropdown\n extraOptionsHeading={copy.filters.optionsHeading}\n clearText={copy.filters.clear}\n clearLabel={copy.filters.clearRange(pill.label)}\n label={pill.label}\n options={pill.options}\n selected={new Set(selected)}\n onToggle={(value, checked) => onTogglePill(fieldId, value, checked)}\n onClear={() => onClearField(fieldId)}\n allLabel={copy.filters.allOption}\n searchable={pill.searchEnabled ? true : undefined}\n searchPlaceholder={copy.filters.optionSearchPlaceholder}\n noResultsLabel={copy.filters.optionsEmpty}\n layout={layout}\n onOpenChange={onOpenChange}\n data-testid={testId}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;AAwEM;AAxDC,SAAS,aACd,SACA,QACA,OACA,cACM;AACN,QAAM,MAAM,IAAI,IAAI,MAAM;AAC1B,QAAM,QAAQ,IAAI,IAAI,KAAK;AAC3B,QAAM,QAAQ,CAAC,UAAU;AACvB,QAAI,CAAC,IAAI,IAAI,KAAK,EAAG,cAAa,SAAS,OAAO,IAAI;AAAA,EACxD,CAAC;AACD,SAAO,QAAQ,CAAC,UAAU;AACxB,QAAI,CAAC,MAAM,IAAI,KAAK,EAAG,cAAa,SAAS,OAAO,KAAK;AAAA,EAC3D,CAAC;AACH;AAGA,IAAM,mBAAmB,CAAC,YAA0B;AAAA,EAClD,IAAI,OAAO;AAAA,EACX,MAAM,OAAO;AAAA,EACb,UAAU,OAAO,YAAY;AAC/B;AAqBO,SAAS,YAA+C;AAAA,EAC7D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS;AACX,GAA2C;AACzC,QAAM,OAAO,iBAAiB;AAC9B,QAAM,SAAS,GAAG,YAAY,WAAW,OAAO;AAChD,MAAI,KAAK,YAAY,YAAY;AAC/B,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,KAAK;AAAA,QACX,aAAa,KAAK;AAAA,QAClB,OAAO,WAAW,YAAY,KAAK,QAAQ;AAAA,QAC3C,WAAW,WAAW;AAAA,QACtB,SAAS,KAAK,QAAQ,IAAI,gBAAgB;AAAA,QAC1C,OAAO;AAAA,QACP,UAAU,CAAC,SAAS,aAAa,SAAS,UAAU,MAAM,YAAY;AAAA,QACtE,YAAY;AAAA;AAAA,IACd;AAAA,EAEJ;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,qBAAqB,KAAK,QAAQ;AAAA,MAClC,WAAW,KAAK,QAAQ;AAAA,MACxB,YAAY,KAAK,QAAQ,WAAW,KAAK,KAAK;AAAA,MAC9C,OAAO,KAAK;AAAA,MACZ,SAAS,KAAK;AAAA,MACd,UAAU,IAAI,IAAI,QAAQ;AAAA,MAC1B,UAAU,CAAC,OAAO,YAAY,aAAa,SAAS,OAAO,OAAO;AAAA,MAClE,SAAS,MAAM,aAAa,OAAO;AAAA,MACnC,UAAU,KAAK,QAAQ;AAAA,MACvB,YAAY,KAAK,gBAAgB,OAAO;AAAA,MACxC,mBAAmB,KAAK,QAAQ;AAAA,MAChC,gBAAgB,KAAK,QAAQ;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,eAAa;AAAA;AAAA,EACf;AAEJ;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/DataViews/data-views-grid-parts.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\nimport { TableFilter } from \"../../layout/TableFilter\";\nimport { Text } from \"../../typography/Text\";\nimport { Box } from \"../../../mui/Box\";\nimport { Stack } from \"../../../mui/Stack\";\n\nimport {\n DataViewsLayoutProvider,\n useDataViewsLayout,\n type DataViewsLayout,\n} from \"./data-views-layout-context\";\nimport { FilterDialog, GridFilterPanel } from \"./data-views-filter-panel\";\nimport { GridMain } from \"./data-views-grid-bodies\";\nimport type { ListGroupConfig } from \"./list-card-rails\";\nimport { InlineFilterControls } from \"./data-views-inline-bar\";\nimport { toOverflowFields, useFilterOverflow, type OverflowSplit } from \"./data-views-overflow\";\nimport { ShellToolbar } from \"./data-views-shell-toolbar\";\nimport type { DisplayPanelView } from \"./data-views-display-panel\";\nimport type { DataViewExport } from \"./data-views-export\";\nimport { useDataViewsCopy } from \"./data-views-copy-context\";\nimport { DataViewsEmpty } from \"./data-views-empty\";\nimport { DataViewsPagination } from \"./data-views-pagination\";\nimport type { BoardConfig } from \"./DataViewsBoard\";\nimport { type ScopeConfig } from \"./data-views-scopes\";\nimport { ScopeTabsSlot } from \"./data-views-scope-tabs\";\nimport { togglePillValues } from \"./data-views-grid-helpers\";\nimport type {\n DataViewCardSelection,\n FilterFieldConfig,\n RangeFieldConfig,\n RangeValue,\n RowAction,\n} from \"./data-views-types\";\nimport type { DataViewsController } from \"./use-data-views-state\";\n\n/* ── Header row ──────────────────────────────────────────────────────────── */\n\n/**\n * The grid's own header row: the page title on the left, the primary page\n * actions on the right.\n *\n * `title` and `headerActions` were DECLARED on `DataViewsGridProps` and\n * `DataViewsTableBaseProps` but never destructured or forwarded — dead props\n * whose types lied to every caller that set them. Wired here rather than\n * deleted: a table that carries its own title keeps the title, the scope tabs\n * and the toolbar as one block, instead of the page having to space them.\n *\n * Renders NOTHING when neither is supplied, so no existing table gains a row.\n */\nfunction GridHeaderRow({\n title,\n headerActions,\n testIdPrefix,\n}: {\n title?: string;\n headerActions?: React.ReactNode;\n testIdPrefix: string;\n}): React.JSX.Element | null {\n if (!title && !headerActions) return null;\n return (\n <Box\n data-testid={`${testIdPrefix}-header`}\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n gap: 2,\n flexWrap: \"wrap\",\n pb: 1.5,\n }}\n >\n {title ? (\n <Text variant=\"heading\" size=\"lg\" as=\"h2\">\n {title}\n </Text>\n ) : (\n <Box />\n )}\n {headerActions && <Box sx={{ display: \"flex\", alignItems: \"center\", gap: 1 }}>{headerActions}</Box>}\n </Box>\n );\n}\n\n/* ── Layout ⇄ view-state mirror ──────────────────────────────────────────── */\n\n/**\n * Mirrors the LIVE layout back into the view state, so saving a view captures\n * the layout the user is actually looking at.\n *\n * A component rather than a callback prop because only something INSIDE the\n * provider can read the layout, and only the controller can write the state.\n * The `stored !== layout` guard is what makes it idempotent: `c` is rebuilt on\n * every render, so an unguarded effect here would patch → re-render → patch.\n *\n * It re-fires when `stored` changes too, which is the case that matters after\n * applying a saved view: `appliedState` REPLACES the state, so a view saved\n * before layouts were captured clears `state.layout`, and the mirror puts the\n * current one back rather than leaving the next save with nothing to store.\n */\nfunction LayoutStateSync<T extends Record<string, unknown>>({\n c,\n}: {\n c: DataViewsController<T>;\n}): null {\n const { layout } = useDataViewsLayout();\n const stored = c.state.layout;\n useEffect(() => {\n if (stored !== layout) c.patch({ layout });\n // `c` is intentionally out of the deps — see the guard above.\n }, [layout, stored]);\n return null;\n}\n\n/* ── Shell (composes toolbar + body + panel) ─────────────────────────────── */\n\n/** The shared filter-surface props consumed by the inline bar, panel, and modal. */\ninterface FilterSurfaceProps<T extends Record<string, unknown>> {\n testIdPrefix: string;\n search: string;\n fields: FilterFieldConfig<T>[];\n rangeFields: RangeFieldConfig<T>[];\n pills: Record<string, string[]>;\n ranges: Record<string, RangeValue>;\n onSearchChange: (value: string) => void;\n onTogglePill: (fieldId: string, value: string, checked: boolean) => void;\n onChangeRange: (fieldId: string, range: RangeValue) => void;\n onClearField: (fieldId: string) => void;\n onClearAll: () => void;\n}\n\n/**\n * Derives the responsive filter state (which surface to show at this width) and\n * the filter-mutation handlers from the controller — pulled out of `GridShell`\n * so the component body stays under the size/complexity budget.\n */\n/** Grouped rather than positional: the list had grown past readability. */\ninterface GridShellFiltersArgs<T extends Record<string, unknown>> {\n c: DataViewsController<T>;\n inlineFilters: boolean;\n fields: FilterFieldConfig<T>[];\n rangeFields: RangeFieldConfig<T>[];\n testIdPrefix: string;\n alwaysShowSearch: boolean;\n /** Is \"Exportar\" on the bar? Half the right cluster's cost when it is not. */\n hasExport: boolean;\n}\n\nfunction useGridShellFilters<T extends Record<string, unknown>>({\n c,\n inlineFilters,\n fields,\n rangeFields,\n testIdPrefix,\n alwaysShowSearch,\n hasExport,\n}: GridShellFiltersArgs<T>): {\n showInline: boolean;\n useModal: boolean;\n inlineVisible: boolean;\n filterProps: FilterSurfaceProps<T>;\n split: OverflowSplit<T>;\n onControlOpenChange: (open: boolean) => void;\n} {\n const { state } = c;\n // How many filter controls are open. A COUNT, not a flag: closing one popover\n // by opening another overlaps, and a boolean would unfreeze mid-handover.\n const [openControls, setOpenControls] = useState(0);\n // ONE measurement for the whole shell: the filter row decides which controls\n // it can keep, and the TOOLBAR needs the same answer to drop its labels.\n // Measuring twice would let the two disagree at the crossover width.\n const split = useFilterOverflow(\n toOverflowFields(fields, rangeFields),\n state.pills,\n c.ranges,\n openControls > 0,\n hasExport,\n );\n // THE BAR RENDERS AT EVERY WIDTH. It used to be swapped for a full-screen\n // filter MODAL below `lg`, which was the responsive strategy before the\n // measured ladder existed — and the two now do the same job, badly together:\n // the modal took the bar away at 1199px, and with it the very degradation\n // (labels off, search collapsed, filters into \"Mais\") that exists to make a\n // narrow bar work. So a 900px window got no bar and no ladder, and the\n // operator lost the search entirely rather than gaining a compact one.\n //\n // The ladder is measured, so it already covers every width the modal did.\n const showInline = inlineFilters;\n const useModal = false;\n const inlineVisible =\n showInline &&\n (alwaysShowSearch || fields.length > 0 || rangeFields.length > 0 || state.search !== \"\");\n const filterProps: FilterSurfaceProps<T> = {\n testIdPrefix,\n search: state.search,\n fields,\n rangeFields,\n pills: state.pills,\n ranges: c.ranges,\n onSearchChange: (value) => c.patch({ search: value }),\n onTogglePill: (fieldId, value, checked) =>\n c.patch({ pills: { ...state.pills, [fieldId]: togglePillValues(state.pills[fieldId], value, checked) } }),\n onChangeRange: (fieldId, range) => c.patch({ ranges: { ...c.ranges, [fieldId]: range } }),\n onClearField: (fieldId) => c.patch({ pills: { ...state.pills, [fieldId]: [] } }),\n onClearAll: () => c.patch({ search: \"\", pills: {}, ranges: {} }),\n };\n return {\n showInline,\n useModal,\n inlineVisible,\n filterProps,\n split,\n onControlOpenChange: (open) =>\n setOpenControls((count) => Math.max(0, count + (open ? 1 : -1))),\n };\n}\n\ninterface GridShellProps<T extends Record<string, unknown>> {\n c: DataViewsController<T>;\n rows: T[];\n fields: FilterFieldConfig<T>[];\n rangeFields: RangeFieldConfig<T>[];\n getRowId: (row: T) => string | number;\n testIdPrefix: string;\n dataTestId?: string;\n emptyState?: React.ReactNode;\n toolbarRightSlot?: React.ReactNode;\n rowActions?: RowAction<T>[];\n bulkActions?: (selectedRows: T[], clearSelection: () => void) => React.ReactNode;\n /** Opt-in \"Grade\" (cards) layout: renders each row as an entity-supplied card.\n * Omit ⇒ table only (no layout toggle). */\n renderCard?: (row: T, selection: DataViewCardSelection) => React.ReactNode;\n /**\n * Opt-in \"Quadro\" (board) layout. Needs `renderCard` too — the board reuses\n * the entity's card, so a board config without one simply offers no board\n * rather than failing: that is a config gap, not a runtime error.\n */\n board?: BoardConfig<T>;\n /** Opt-in \"Lista\" layout: one full-width, entity-rendered row per record. */\n renderListRow?: (row: T, selection: DataViewCardSelection) => React.ReactNode;\n /** The Lista's shared columns, so its rows line up by construction. */\n listGroup?: ListGroupConfig<T>;\n /** The page-level partition rendered as tabs under the toolbar. */\n scopes?: ScopeConfig[];\n /**\n * The row field the scopes partition by. Compared against the board's\n * `groupBy` so the two are not shown as the same partition twice.\n */\n scopeFieldId?: string;\n /** Per-sort-field value kind, so directions read in the column's own terms. */\n sortKinds?: Record<string, string>;\n /** The saved-view chrome bracketing the Exibir panel. */\n displayView?: DisplayPanelView;\n /** Injected export — the host re-queries; the grid never fetches. */\n exportConfig?: DataViewExport;\n /** Page title in the grid's own header row, above the scopes + toolbar. */\n title?: string;\n /** Primary page actions rendered at the header row's right. */\n headerActions?: React.ReactNode;\n /** Which layout to show first when the user has expressed no preference (default \"table\"). */\n defaultLayout?: DataViewsLayout;\n /** Pin to `defaultLayout`, ignoring the remembered preference (stories/docs). */\n ignoreStoredLayout?: boolean;\n /**\n * Opt into the responsive inline filter UX: a collapsible filter row below the\n * toolbar on wide screens and a modal on narrow ones. When false (default),\n * keep the classic slide-in filter panel opened by the \"Filtros\" button.\n */\n inlineFilters?: boolean;\n /**\n * Keep the inline search box on screen even with no filter fields and no\n * active search. Without it a grid whose only filter IS the search (`fields`\n * empty) can never OFFER one: the row renders only once a search exists, so\n * the operator would have to hand-edit the URL to start one. Default false —\n * every grid that ships filter fields already shows the row.\n */\n alwaysShowSearch?: boolean;\n}\n\n/** The scrollable content region: scope tabs, toolbar, filter bar, body, pager. */\nfunction ShellStack<T extends Record<string, unknown>>({\n props,\n filters,\n}: {\n props: GridShellProps<T>;\n filters: ReturnType<typeof useGridShellFilters<T>>;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n const { c, testIdPrefix, dataTestId, scopes = [], emptyState, inlineFilters = false } = props;\n const { showInline, useModal, inlineVisible, filterProps, split, onControlOpenChange } = filters;\n // The grid renders the FILTERED empty state itself — it is the only party\n // that knows a filter is applied. See {@link DataViewsEmpty}.\n const body = (\n <DataViewsEmpty\n filtered={c.activeFilterCount > 0 || c.state.search !== \"\"}\n onClearFilters={() => c.patch({ search: \"\", pills: {}, ranges: {} })}\n emptyState={emptyState}\n testIdPrefix={testIdPrefix}\n />\n );\n return (\n <TableFilter\n copy={copy.tableFilter} open={c.filterOpen} onOpenChange={c.setFilterOpen}\n hasActiveFilters={c.activeFilterCount > 0}\n >\n <Stack spacing={0} data-testid={dataTestId ? `${dataTestId}-container` : undefined}>\n <GridHeaderRow title={props.title} headerActions={props.headerActions} testIdPrefix={testIdPrefix} />\n <ShellToolbar\n c={c}\n rows={props.rows}\n sortKinds={props.sortKinds}\n displayView={props.displayView}\n exportConfig={props.exportConfig}\n filterControls={\n inlineVisible ? (\n <InlineFilterControls\n {...filterProps} split={split} activeFilterCount={c.activeFilterCount}\n onControlOpenChange={onControlOpenChange}\n />\n ) : undefined\n }\n barRef={split.barRef}\n testIdPrefix={testIdPrefix}\n rowActions={props.rowActions}\n bulkActions={props.bulkActions}\n toolbarRightSlot={props.toolbarRightSlot}\n compactControls={showInline && split.compactControls}\n counterHidden={showInline && split.counterHidden}\n showInline={showInline}\n />\n <ScopeTabsSlot\n scopes={scopes} scopeFieldId={props.scopeFieldId} board={props.board}\n value={c.scope} onChange={c.setScope} counts={c.scopeCounts}\n testIdPrefix={testIdPrefix}\n />\n <TableFilter.Layout>\n <TableFilter.Main>\n <GridMain\n c={c}\n getRowId={props.getRowId}\n renderCard={props.renderCard}\n renderListRow={props.renderListRow}\n listGroup={props.listGroup}\n board={props.board}\n dataTestId={dataTestId}\n testIdPrefix={testIdPrefix}\n emptyState={body}\n />\n <DataViewsPagination c={c} testIdPrefix={testIdPrefix} />\n </TableFilter.Main>\n {inlineFilters ? null : <GridFilterPanel {...filterProps} />}\n </TableFilter.Layout>\n {useModal && <FilterDialog open={c.filterOpen} onClose={() => c.setFilterOpen(false)} {...filterProps} />}\n </Stack>\n </TableFilter>\n );\n}\n\nexport function GridShell<T extends Record<string, unknown>>(props: GridShellProps<T>): React.JSX.Element {\n const {\n c,\n fields,\n rangeFields,\n testIdPrefix,\n renderCard,\n renderListRow,\n board,\n defaultLayout,\n ignoreStoredLayout,\n inlineFilters = false,\n alwaysShowSearch = false,\n } = props;\n const filters = useGridShellFilters({\n c,\n inlineFilters,\n fields,\n rangeFields,\n testIdPrefix,\n alwaysShowSearch,\n hasExport: props.exportConfig !== undefined,\n });\n return (\n <DataViewsLayoutProvider\n canUseCards={Boolean(renderCard)}\n canUseList={Boolean(renderListRow)}\n // The board reuses the entity's card, so it needs BOTH — a `board` with no\n // `renderCard` offers no board rather than throwing.\n canUseBoard={Boolean(board && renderCard)}\n defaultLayout={defaultLayout}\n ignoreStoredLayout={ignoreStoredLayout}\n viewLayout={c.state.layout}\n >\n <LayoutStateSync c={c} />\n <ShellStack props={props} filters={filters} />\n </DataViewsLayoutProvider>\n );\n}\n"],"mappings":"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","names":["default"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/DataViews/use-data-views-state.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\n\nimport { type GridColumn } from \"../DataGrid\";\nimport { type SortFieldDefinition } from \"../../layout/ContentToolbar\";\n\nimport { filterRows } from \"./data-views-filter\";\nimport {\n useColumnsWithActions,\n useRenderedColumns,\n type HideableColumn,\n} from \"./data-views-columns\";\nimport { computeActiveFilterCount, defaultSortFields } from \"./data-views-grid-helpers\";\nimport { resolveScope, useScopeConfigChecks, type ScopeConfig } from \"./data-views-scopes\";\nimport { serverDerived, useServerQuery } from \"./data-views-server-query\";\nimport { useSelection } from \"./data-views-selection\";\nimport {\n emptyViewState,\n type DataViewColumn,\n type DataViewServer,\n type DataViewState,\n type DataViewSyncState,\n type FilterFieldConfig,\n type RangeFieldConfig,\n type DataViewQuery,\n type RangeValue,\n type RowAction,\n} from \"./data-views-types\";\n\nexport type { HideableColumn };\n\n/** Inputs the hook needs to own the view state + derived data. */\ninterface UseDataViewsStateArgs<T extends Record<string, unknown>> {\n rows: T[];\n columns: DataViewColumn<T>[];\n fields: FilterFieldConfig<T>[];\n rangeFields: RangeFieldConfig<T>[];\n getRowId: (row: T) => string | number;\n onRowClick?: (row: T) => void;\n appliedState?: DataViewState;\n /**\n * URL-driven controls (search/pills/ranges/sort) that re-apply on reference\n * change while preserving column visibility — see {@link DataViewSyncState}.\n */\n syncState?: DataViewSyncState;\n onStateChange?: (state: DataViewState) => void;\n testIdPrefix: string;\n sortFields?: SortFieldDefinition[];\n rowActions?: RowAction<T>[];\n rowActionsLeading?: (row: T) => React.ReactNode;\n /** Bespoke per-row menu rendered in the actions column instead of the auto kebab. */\n renderRowMenu?: (row: T) => React.ReactNode;\n /**\n * When present, the table is BACKEND-driven: `rows` is already the current\n * page (no in-browser filter/sort/paginate), and query changes re-fetch via\n * `server.onQueryChange`. Omit for the legacy client-side behaviour.\n */\n server?: DataViewServer;\n /**\n * The page-level partition rendered as tabs under the toolbar. Requires server\n * mode: a scope is applied at the BACKEND, never in the browser.\n */\n scopes?: ScopeConfig[];\n /**\n * The row field the scopes partition by, when the host knows it — used only to\n * reject the pill/scope clash at setup. See `assertNoScopePillOverlap`.\n */\n scopeFieldId?: string;\n}\n\n/** Everything the DataViewsGrid render needs; keeps the component itself thin. */\nexport interface DataViewsController<T extends Record<string, unknown>> {\n state: DataViewState;\n ranges: Record<string, RangeValue>;\n patch: (next: Partial<DataViewState>) => void;\n matched: T[];\n gridColumns: GridColumn<T>[];\n /** Columns eligible for the show/hide menu (hideable, with a text header). */\n hideableColumns: HideableColumn[];\n /** Every column id in reading order — what the Colunas tab reorders. */\n columnOrder: string[];\n /** Toggle one column's visibility in the current view state. */\n toggleColumn: (id: string, visible: boolean) => void;\n selectedIds: Set<string | number>;\n setSelectedIds: (ids: Set<string | number>) => void;\n /**\n * Add/remove ONE id from the selection. The card and board bodies both need\n * it, so it lives here rather than being written out twice at the call sites —\n * one selection model, one toggle, identical in every layout.\n */\n toggleId: (id: string | number) => void;\n selectedRows: T[];\n clearSelection: () => void;\n selectAll: () => void;\n filterOpen: boolean;\n setFilterOpen: (open: boolean) => void;\n resolvedSortFields: SortFieldDefinition[];\n activeSortField: string;\n activeSortOrder: \"asc\" | \"desc\";\n activeFilterCount: number;\n /** True when the table is backend-driven (server mode). */\n serverMode: boolean;\n /** Server-mode pagination: 1-based current page (1 when client mode). */\n serverPage: number;\n /** Server-mode pagination: total pages (1 when client mode). */\n serverPageCount: number;\n /** Server-mode: total matched rows across all pages (drives the counter). */\n serverTotalCount: number;\n /** Server-mode: request a specific 1-based page (re-fetches via onQueryChange). */\n changePage: (page: number) => void;\n /**\n * The active scope id, RESOLVED against the declared scopes (see\n * `resolveScope`). `undefined` when the table declares none — which is what\n * keeps `scope` out of the emitted query for every table that never opted in.\n */\n scope: string | undefined;\n /** Select a scope: re-fetches from page 1 and drops the selection. */\n setScope: (id: string) => void;\n /**\n * The query the grid is currently showing. Read by Export, which hands it\n * back to the host UNPAGINATED — so an export follows the filters rather\n * than the 25 rows that happen to be loaded.\n */\n currentQuery: DataViewQuery;\n /**\n * Server-supplied per-scope totals, passed through verbatim, or `undefined`\n * when the server omits them. NEVER falls back to counting `matched`: a count\n * off the loaded page is wrong under pagination, which is the bug scopes fix.\n */\n scopeCounts?: Record<string, number>;\n}\n\n/**\n * The rows to render: in server mode `rows` is already the current page (the\n * backend filtered/sorted/paginated), so it passes through untouched; otherwise\n * the client search/pill/range filter narrows it.\n */\nfunction useMatchedRows<T extends Record<string, unknown>>(\n server: DataViewServer | undefined,\n rows: T[],\n columns: DataViewColumn<T>[],\n fields: FilterFieldConfig<T>[],\n rangeFields: RangeFieldConfig<T>[],\n state: DataViewState,\n): T[] {\n return useMemo(\n () =>\n server\n ? rows\n : filterRows(\n rows,\n columns,\n fields,\n { search: state.search, pills: state.pills, ranges: state.ranges },\n rangeFields,\n ),\n [server, rows, columns, fields, rangeFields, state.search, state.pills, state.ranges],\n );\n}\n\n/** Add or remove one column id from the visible-columns list. */\nfunction nextVisibleColumns(current: string[], id: string, visible: boolean): string[] {\n const without = current.filter((colId) => colId !== id);\n return visible ? [...without, id] : without;\n}\n\n/**\n * Reconcile external state changes and surface every change to the caller:\n * - `appliedState` (a saved view) fully REPLACES the state — it is columns-\n * authoritative, so applying a view restores its filters, sort AND columns.\n * - `syncState` (URL-driven controls) MERGES over the current state, leaving\n * `visibleColumns` untouched — so browser back/forward or a same-route\n * deep-link re-applies search/pills/sort without wiping a user's hidden columns.\n * Each channel only fires when its own reference changes (after the first run).\n */\nfunction useStateSyncEffects(\n appliedState: DataViewState | undefined,\n syncState: DataViewSyncState | undefined,\n state: DataViewState,\n onStateChange: ((state: DataViewState) => void) | undefined,\n setState: React.Dispatch<React.SetStateAction<DataViewState>>,\n): void {\n const firstApplied = useRef(true);\n useEffect(() => {\n if (firstApplied.current) {\n firstApplied.current = false;\n return;\n }\n if (appliedState) setState(appliedState);\n // `setState` is a stable useState setter (passed in), included to satisfy\n // exhaustive-deps; the effect still only re-applies when `appliedState` changes.\n }, [appliedState, setState]);\n const firstSync = useRef(true);\n useEffect(() => {\n if (firstSync.current) {\n firstSync.current = false;\n return;\n }\n // Merge — keep the current `visibleColumns` (never URL-carried); only the\n // URL-driven controls change.\n if (syncState) setState((prev) => ({ ...prev, ...syncState }));\n }, [syncState, setState]);\n useEffect(() => {\n onStateChange?.(state);\n }, [state, onStateChange]);\n}\n\n/**\n * The seed view state: a saved view (`appliedState`) or a pristine all-columns\n * one, with the URL-driven controls (`syncState`) merged over it so a deep-linked\n * filter or scope is reflected on the FIRST render. Column visibility is never\n * URL-carried, so the merge cannot wipe it.\n */\nfunction seedState(\n appliedState: DataViewState | undefined,\n syncState: DataViewSyncState | undefined,\n allColumnIds: string[],\n): DataViewState {\n const seed = appliedState ?? emptyViewState(allColumnIds);\n return syncState ? { ...seed, ...syncState } : seed;\n}\n\n/**\n * Selecting a scope: record it, then DROP the selection.\n *\n * A scope change renders a different result set. A selection that survived it\n * would let a bulk action run against rows the user can no longer see — the\n * same reason changing any other part of the query clears it.\n */\nfunction makeSetScope(\n patch: (next: Partial<DataViewState>) => void,\n clearSelection: () => void,\n): (id: string) => void {\n return (id) => {\n patch({ scope: id });\n clearSelection();\n };\n}\n\n/**\n * The declared column ids in the stored reading order — the same tolerance\n * `applyOrder` uses, so the Colunas tab lists exactly what the grid renders.\n */\nfunction orderedColumnIds(allColumnIds: string[], order: string[] | undefined): string[] {\n if (!order || order.length === 0) return allColumnIds;\n const declared = new Set(allColumnIds);\n const kept = order.filter((id) => declared.has(id));\n const seen = new Set(kept);\n return [...kept, ...allColumnIds.filter((id) => !seen.has(id))];\n}\n\n/**\n * The query the grid is currently SHOWING — the same shape `onQueryChange`\n * emits, kept as a value so a host control (Exportar) can re-run it against the\n * backend rather than reading the loaded rows.\n */\nfunction liveQuery(\n state: DataViewState,\n scope: string | undefined,\n server: DataViewServer | undefined,\n matchedCount: number,\n): DataViewQuery {\n return {\n search: state.search,\n pills: state.pills,\n ranges: state.ranges ?? {},\n sortBy: state.sortBy,\n // Absent, not undefined, when the table declares no scopes.\n ...(scope !== undefined ? { scope } : {}),\n page: server?.page ?? 1,\n // Client mode has one page, and it is everything that matched.\n pageSize: server?.pageSize ?? matchedCount,\n };\n}\n\n/** The toolbar's active sort, read off the state's FIRST sort entry. */\nfunction activeSortOf(\n state: DataViewState,\n resolvedSortFields: SortFieldDefinition[],\n): { activeSortField: string; activeSortOrder: \"asc\" | \"desc\" } {\n const active = state.sortBy?.[0];\n return {\n activeSortField: active?.id ?? resolvedSortFields[0]?.value ?? \"\",\n activeSortOrder: active?.dir === \"desc\" ? \"desc\" : \"asc\",\n };\n}\n\n/**\n * Owns the DataViews view state (search/pills/ranges/sort/visibleColumns),\n * multi-select, the filter-panel toggle, and every derived value (filtered rows,\n * grid columns, active sort/filter counts). Extracted from the component so the\n * render stays within the size/complexity budget.\n */\nexport function useDataViewsState<T extends Record<string, unknown>>({\n rows,\n columns,\n fields,\n rangeFields,\n getRowId,\n onRowClick,\n appliedState,\n syncState,\n onStateChange,\n testIdPrefix,\n sortFields,\n rowActions,\n rowActionsLeading,\n renderRowMenu,\n server,\n scopes = [],\n scopeFieldId,\n}: UseDataViewsStateArgs<T>): DataViewsController<T> {\n useScopeConfigChecks(scopes, fields.map((field) => field.id), scopeFieldId, server !== undefined);\n const { columnsWithActions, allColumnIds } = useColumnsWithActions(columns, {\n rowActions,\n rowActionsLeading,\n renderRowMenu,\n testIdPrefix,\n getRowId,\n });\n // Seed once — see {@link seedState}.\n const [state, setState] = useState<DataViewState>(() =>\n seedState(appliedState, syncState, allColumnIds),\n );\n const [filterOpen, setFilterOpen] = useState(false);\n\n useStateSyncEffects(appliedState, syncState, state, onStateChange, setState);\n\n const patch = (next: Partial<DataViewState>): void => setState((prev) => ({ ...prev, ...next }));\n const matched = useMatchedRows(server, rows, columns, fields, rangeFields, state);\n // Resolved at READ time, every render: a stale deep link or a saved view naming\n // a scope that has since been removed falls back to the first declared scope\n // instead of putting an id the backend rejects on the wire.\n const scope = resolveScope(scopes, state.scope);\n // Server mode: re-fetch on query change (page resets to 1) + drive pagination.\n const { changePage } = useServerQuery(server, state, scope);\n const { gridColumns, hideableColumns } = useRenderedColumns(\n columnsWithActions,\n state.visibleColumns,\n onRowClick,\n state.order,\n );\n const toggleColumn = (id: string, visible: boolean): void =>\n setState((prev) => ({ ...prev, visibleColumns: nextVisibleColumns(prev.visibleColumns, id, visible) }));\n const selection = useSelection(matched, getRowId);\n const resolvedSortFields = sortFields ?? defaultSortFields(columns);\n\n return {\n state,\n ranges: state.ranges ?? {},\n patch,\n matched,\n gridColumns,\n hideableColumns,\n columnOrder: orderedColumnIds(allColumnIds, state.order),\n toggleColumn,\n ...selection,\n filterOpen,\n setFilterOpen,\n resolvedSortFields,\n ...activeSortOf(state, resolvedSortFields),\n activeFilterCount: computeActiveFilterCount(state, fields, rangeFields),\n ...serverDerived(server, matched.length),\n changePage,\n scope,\n setScope: makeSetScope(patch, selection.clearSelection),\n currentQuery: liveQuery(state, scope, server, matched.length),\n // Verbatim, or absent. See DataViewServer.scopeCounts for why there is no\n // client-side fallback.\n scopeCounts: server?.scopeCounts,\n };\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/form/CategorySelect/useCategoryCommit.ts"],"sourcesContent":["import { useCallback, useMemo } from 'react';\n\nimport type { CategoryGroup, CategorySelectProps } from './CategorySelect.types';\nimport type { CategorySelectState } from './useCategorySelect';\n\n/** The verbs a row, a footer button or a key press can invoke. */\nexport interface CategoryActions {\n /** Publish the draft to the caller and close (multi-select). */\n commit: () => void;\n /** Choose one id and close (single-select). */\n pick: (id: string) => void;\n /** Close without publishing, returning focus to the trigger. */\n cancel: () => void;\n /** Empty the APPLIED selection straight from the closed trigger. */\n clearSelection: () => void;\n activateCategory: (group: CategoryGroup) => void;\n activateSubcategory: (id: string) => void;\n}\n\ninterface UseCategoryCommitArgs {\n props: CategorySelectProps;\n state: CategorySelectState;\n single: boolean;\n allowParentSelection: boolean;\n}\n\n/**\n * The commit semantics, which are the one thing the two modes genuinely differ\n * on: multi-select batches into a draft and publishes on Apply, single-select\n * publishes the moment a row is chosen.\n */\nexport function useCategoryCommit({\n props,\n state,\n single,\n allowParentSelection,\n}: UseCategoryCommitArgs): CategoryActions {\n const { closePanel, triggerRef, toggleCategory, toggleSubcategory, toggleExpanded } = state;\n\n const closeAndRefocus = useCallback(() => {\n closePanel();\n triggerRef.current?.focus();\n }, [closePanel, triggerRef]);\n\n const commit = useCallback(() => {\n if (props.mode === 'single') return;\n props.onChange([...state.draft]);\n closeAndRefocus();\n }, [props, state.draft, closeAndRefocus]);\n\n const pick = useCallback(\n (id: string) => {\n if (props.mode !== 'single') return;\n props.onChange(id);\n closeAndRefocus();\n },\n [props, closeAndRefocus],\n );\n\n const clearSelection = useCallback(() => {\n if (props.mode === 'single') props.onChange(null);\n else props.onChange([]);\n }, [props]);\n\n // A category row means different things per mode: pick the whole category,\n // tick all its leaves, or — when it is only a heading — just fold it.\n const activateCategory = useCallback(\n (group: CategoryGroup) => {\n if (allowParentSelection && single) {\n pick(group.category.id);\n return;\n }\n if (allowParentSelection) {\n toggleCategory(group);\n return;\n }\n toggleExpanded(group.category.id);\n },\n [allowParentSelection, single, pick, toggleCategory, toggleExpanded],\n );\n\n const activateSubcategory = useCallback(\n (id: string) => {\n if (single) pick(id);\n else toggleSubcategory(id);\n },\n [single, pick, toggleSubcategory],\n );\n\n return useMemo(\n () => ({\n commit,\n pick,\n cancel: closeAndRefocus,\n clearSelection,\n activateCategory,\n activateSubcategory,\n }),\n [commit, pick, closeAndRefocus, clearSelection, activateCategory, activateSubcategory],\n );\n}\n"],"mappings":";AAAA,SAAS,aAAa,eAAe;AA+B9B,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2C;AACzC,QAAM,EAAE,YAAY,YAAY,gBAAgB,mBAAmB,eAAe,IAAI;AAEtF,QAAM,kBAAkB,YAAY,MAAM;AACxC,eAAW;AACX,eAAW,SAAS,MAAM;AAAA,EAC5B,GAAG,CAAC,YAAY,UAAU,CAAC;AAE3B,QAAM,SAAS,YAAY,MAAM;AAC/B,QAAI,MAAM,SAAS,SAAU;AAC7B,UAAM,SAAS,CAAC,GAAG,MAAM,KAAK,CAAC;AAC/B,oBAAgB;AAAA,EAClB,GAAG,CAAC,OAAO,MAAM,OAAO,eAAe,CAAC;AAExC,QAAM,OAAO;AAAA,IACX,CAAC,OAAe;AACd,UAAI,MAAM,SAAS,SAAU;AAC7B,YAAM,SAAS,EAAE;AACjB,sBAAgB;AAAA,IAClB;AAAA,IACA,CAAC,OAAO,eAAe;AAAA,EACzB;AAEA,QAAM,iBAAiB,YAAY,MAAM;AACvC,QAAI,MAAM,SAAS,SAAU,OAAM,SAAS,IAAI;AAAA,QAC3C,OAAM,SAAS,CAAC,CAAC;AAAA,EACxB,GAAG,CAAC,KAAK,CAAC;AAIV,QAAM,mBAAmB;AAAA,IACvB,CAAC,UAAyB;AACxB,UAAI,wBAAwB,QAAQ;AAClC,aAAK,MAAM,SAAS,EAAE;AACtB;AAAA,MACF;AACA,UAAI,sBAAsB;AACxB,uBAAe,KAAK;AACpB;AAAA,MACF;AACA,qBAAe,MAAM,SAAS,EAAE;AAAA,IAClC;AAAA,IACA,CAAC,sBAAsB,QAAQ,MAAM,gBAAgB,cAAc;AAAA,EACrE;AAEA,QAAM,sBAAsB;AAAA,IAC1B,CAAC,OAAe;AACd,UAAI,OAAQ,MAAK,EAAE;AAAA,UACd,mBAAkB,EAAE;AAAA,IAC3B;AAAA,IACA,CAAC,QAAQ,MAAM,iBAAiB;AAAA,EAClC;AAEA,SAAO;AAAA,IACL,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,MAAM,iBAAiB,gBAAgB,kBAAkB,mBAAmB;AAAA,EACvF;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/form/CategorySelect/CategoryRows.tsx"],"sourcesContent":["'use client';\n\nimport Box from '@mui/material/Box/index.js';\n\nimport type { CategorySelectCopy } from '../../../copy';\nimport { CheckGlyph, DisclosureGlyph } from './CategoryIcons';\nimport { highlightSegments, type CategoryCheckState } from './category-tree';\nimport {\n checkboxBarSx,\n checkboxSx,\n chevronButtonSx,\n markSx,\n METRICS,\n radioSx,\n rowMetaSx,\n rowNameSx,\n rowSx,\n sectionHeadSx,\n} from './CategorySelect.styles';\nimport type { CategorySelectOption } from './CategorySelect.types';\n\n/** A label with the search hit marked. */\nexport function HighlightedName({\n text,\n query,\n}: {\n text: string;\n query: string;\n}): React.JSX.Element {\n const segments = highlightSegments(text, query);\n return (\n <>\n {segments.map((segment, index) =>\n segment.match ? (\n <Box component=\"mark\" key={`${segment.text}-${index}`} sx={markSx}>\n {segment.text}\n </Box>\n ) : (\n <span key={`${segment.text}-${index}`}>{segment.text}</span>\n ),\n )}\n </>\n );\n}\n\n/** Tri-state checkbox. `partial` draws the bar the prototype uses for \"some\". */\nexport function CategoryCheckbox({ state }: { state: CategoryCheckState }): React.JSX.Element {\n return (\n <Box sx={(theme) => checkboxSx(theme, state)} aria-hidden=\"true\">\n {state === 'on' && <CheckGlyph />}\n {state === 'partial' && <Box sx={checkboxBarSx} />}\n </Box>\n );\n}\n\nexport function CategoryRadio({ on }: { on: boolean }): React.JSX.Element {\n return <Box sx={(theme) => radioSx(theme, on)} aria-hidden=\"true\" />;\n}\n\ninterface CategoryHeadRowProps {\n option: CategorySelectOption;\n query: string;\n expanded: boolean;\n active: boolean;\n sheet: boolean;\n /** Absent in leaf-only mode, where the category is a heading with no checkbox. */\n checkState?: CategoryCheckState;\n selectedCount: number;\n showCounts: boolean;\n onToggleExpanded: () => void;\n onActivate: () => void;\n dataTestId: string;\n copy: CategorySelectCopy;\n}\n\n/** The trailing number: how many children are picked, else the item count. */\nfunction rowMeta(\n selectedCount: number,\n showCounts: boolean,\n count: number | undefined,\n): { text: string; selected: boolean } | null {\n if (selectedCount > 0) return { text: String(selectedCount), selected: true };\n if (showCounts && count !== undefined) return { text: String(count), selected: false };\n return null;\n}\n\n/**\n * A top-level category row: disclosure chevron, optional checkbox, name, meta.\n *\n * The chevron is its OWN button inside the row button — clicking it only folds,\n * while clicking the row does the row's job (expand as a heading, or tick when\n * parents are selectable).\n */\nexport function CategoryHeadRow({\n option,\n query,\n expanded,\n active,\n sheet,\n checkState,\n selectedCount,\n showCounts,\n onToggleExpanded,\n onActivate,\n dataTestId,\n copy,\n}: CategoryHeadRowProps): React.JSX.Element {\n const meta = rowMeta(selectedCount, showCounts, option.count);\n return (\n <Box\n component=\"div\"\n role={checkState ? 'option' : 'button'}\n aria-selected={checkState ? checkState === 'on' : undefined}\n aria-expanded={expanded}\n data-testid={`${dataTestId}-category-${option.id}`}\n sx={(theme) => rowSx(theme, active, sheet)}\n onClick={onActivate}\n >\n <Box\n component=\"button\"\n type=\"button\"\n tabIndex={-1}\n aria-label={\n expanded ? copy.collapseCategory(option.name) : copy.expandCategory(option.name)\n }\n data-testid={`${dataTestId}-expand-${option.id}`}\n sx={(theme) => chevronButtonSx(theme, expanded)}\n onClick={(event: React.MouseEvent) => {\n event.stopPropagation();\n onToggleExpanded();\n }}\n >\n <DisclosureGlyph />\n </Box>\n {checkState ? (\n <CategoryCheckbox state={checkState} />\n ) : (\n <Box sx={{ width: METRICS.boxSize, flex: '0 0 auto' }} />\n )}\n <Box component=\"span\" sx={(theme) => rowNameSx(theme, true)}>\n <HighlightedName text={option.name} query={query} />\n </Box>\n {meta && (\n <Box component=\"span\" sx={(theme) => rowMetaSx(theme, meta.selected)}>\n {meta.text}\n </Box>\n )}\n </Box>\n );\n}\n\ninterface SubcategoryRowProps {\n option: CategorySelectOption;\n query: string;\n selected: boolean;\n active: boolean;\n sheet: boolean;\n /** Single-select mode draws a radio; multi-select draws a checkbox. */\n single: boolean;\n showCounts: boolean;\n onActivate: () => void;\n dataTestId: string;\n}\n\nexport function SubcategoryRow({\n option,\n query,\n selected,\n active,\n sheet,\n single,\n showCounts,\n onActivate,\n dataTestId,\n}: SubcategoryRowProps): React.JSX.Element {\n return (\n <Box\n component=\"div\"\n role=\"option\"\n aria-selected={selected}\n data-testid={`${dataTestId}-option-${option.id}`}\n sx={(theme) => ({ ...rowSx(theme, active, sheet), paddingLeft: '14px' })}\n onClick={onActivate}\n >\n <Box sx={{ width: METRICS.chevronButton, flex: '0 0 auto' }} />\n {single ? (\n <CategoryRadio on={selected} />\n ) : (\n <CategoryCheckbox state={selected ? 'on' : 'off'} />\n )}\n <Box component=\"span\" sx={(theme) => rowNameSx(theme, false)}>\n <HighlightedName text={option.name} query={query} />\n </Box>\n {showCounts && option.count !== undefined && (\n <Box component=\"span\" sx={(theme) => rowMetaSx(theme, false)}>\n {option.count}\n </Box>\n )}\n </Box>\n );\n}\n\n/** The category as a non-interactive heading (single-select, leaf-only). */\nexport function CategorySectionHeading({\n option,\n query,\n}: {\n option: CategorySelectOption;\n query: string;\n}): React.JSX.Element {\n return (\n <Box sx={sectionHeadSx}>\n <HighlightedName text={option.name} query={query} />\n </Box>\n );\n}\n"],"mappings":"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","names":[]}