@navikt/ds-react 8.10.2 → 8.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/data/table/column-header/DataTableColumnHeader.js +3 -1
- package/cjs/data/table/column-header/DataTableColumnHeader.js.map +1 -1
- package/cjs/data/table/column-header/useTableColumnResize.d.ts +24 -2
- package/cjs/data/table/column-header/useTableColumnResize.js +31 -15
- package/cjs/data/table/column-header/useTableColumnResize.js.map +1 -1
- package/cjs/data/table/helpers/collectTableRowEntries.d.ts +10 -2
- package/cjs/data/table/helpers/collectTableRowEntries.js +25 -17
- package/cjs/data/table/helpers/collectTableRowEntries.js.map +1 -1
- package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.d.ts +46 -0
- package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.js +112 -0
- package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.js.map +1 -0
- package/cjs/data/table/helpers/selection/getMultipleSelectProps.d.ts +3 -2
- package/cjs/data/table/helpers/selection/getMultipleSelectProps.js +43 -19
- package/cjs/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
- package/cjs/data/table/helpers/selection/selection.types.d.ts +1 -0
- package/cjs/data/table/helpers/table-keyboard.d.ts +1 -2
- package/cjs/data/table/helpers/table-keyboard.js +1 -2
- package/cjs/data/table/helpers/table-keyboard.js.map +1 -1
- package/cjs/data/table/hooks/useTableExpansion.d.ts +1 -3
- package/cjs/data/table/hooks/useTableExpansion.js +7 -1
- package/cjs/data/table/hooks/useTableExpansion.js.map +1 -1
- package/cjs/data/table/hooks/useTableItems.d.ts +7 -3
- package/cjs/data/table/hooks/useTableItems.js +18 -7
- package/cjs/data/table/hooks/useTableItems.js.map +1 -1
- package/cjs/data/table/hooks/useTableSelection.d.ts +3 -2
- package/cjs/data/table/hooks/useTableSelection.js +5 -4
- package/cjs/data/table/hooks/useTableSelection.js.map +1 -1
- package/cjs/data/table/root/DataTable.types.d.ts +5 -4
- package/cjs/data/table/root/DataTableAuto.d.ts +9 -1
- package/cjs/data/table/root/DataTableAuto.js +50 -50
- package/cjs/data/table/root/DataTableAuto.js.map +1 -1
- package/cjs/data/table/root/DataTableRoot.js +2 -3
- package/cjs/data/table/root/DataTableRoot.js.map +1 -1
- package/cjs/form/checkbox/Checkbox.js +1 -0
- package/cjs/form/checkbox/Checkbox.js.map +1 -1
- package/cjs/form/radio/Radio.js +7 -1
- package/cjs/form/radio/Radio.js.map +1 -1
- package/cjs/modal/types.d.ts +8 -4
- package/esm/data/table/column-header/DataTableColumnHeader.js +3 -1
- package/esm/data/table/column-header/DataTableColumnHeader.js.map +1 -1
- package/esm/data/table/column-header/useTableColumnResize.d.ts +24 -2
- package/esm/data/table/column-header/useTableColumnResize.js +32 -16
- package/esm/data/table/column-header/useTableColumnResize.js.map +1 -1
- package/esm/data/table/helpers/collectTableRowEntries.d.ts +10 -2
- package/esm/data/table/helpers/collectTableRowEntries.js +25 -17
- package/esm/data/table/helpers/collectTableRowEntries.js.map +1 -1
- package/esm/data/table/helpers/selection/SelectionSubtreeHelper.d.ts +46 -0
- package/esm/data/table/helpers/selection/SelectionSubtreeHelper.js +109 -0
- package/esm/data/table/helpers/selection/SelectionSubtreeHelper.js.map +1 -0
- package/esm/data/table/helpers/selection/getMultipleSelectProps.d.ts +3 -2
- package/esm/data/table/helpers/selection/getMultipleSelectProps.js +43 -19
- package/esm/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
- package/esm/data/table/helpers/selection/selection.types.d.ts +1 -0
- package/esm/data/table/helpers/table-keyboard.d.ts +1 -2
- package/esm/data/table/helpers/table-keyboard.js +1 -2
- package/esm/data/table/helpers/table-keyboard.js.map +1 -1
- package/esm/data/table/hooks/useTableExpansion.d.ts +1 -3
- package/esm/data/table/hooks/useTableExpansion.js +7 -1
- package/esm/data/table/hooks/useTableExpansion.js.map +1 -1
- package/esm/data/table/hooks/useTableItems.d.ts +7 -3
- package/esm/data/table/hooks/useTableItems.js +18 -7
- package/esm/data/table/hooks/useTableItems.js.map +1 -1
- package/esm/data/table/hooks/useTableSelection.d.ts +3 -2
- package/esm/data/table/hooks/useTableSelection.js +5 -4
- package/esm/data/table/hooks/useTableSelection.js.map +1 -1
- package/esm/data/table/root/DataTable.types.d.ts +5 -4
- package/esm/data/table/root/DataTableAuto.d.ts +9 -1
- package/esm/data/table/root/DataTableAuto.js +51 -51
- package/esm/data/table/root/DataTableAuto.js.map +1 -1
- package/esm/data/table/root/DataTableRoot.js +3 -4
- package/esm/data/table/root/DataTableRoot.js.map +1 -1
- package/esm/form/checkbox/Checkbox.js +1 -0
- package/esm/form/checkbox/Checkbox.js.map +1 -1
- package/esm/form/radio/Radio.js +7 -1
- package/esm/form/radio/Radio.js.map +1 -1
- package/esm/modal/types.d.ts +8 -4
- package/package.json +3 -3
- package/src/data/table/column-header/DataTableColumnHeader.tsx +5 -1
- package/src/data/table/column-header/useTableColumnResize.ts +73 -17
- package/src/data/table/helpers/collectTableRowEntries.ts +57 -25
- package/src/data/table/helpers/selection/SelectionSubtreeHelper.test.ts +66 -0
- package/src/data/table/helpers/selection/SelectionSubtreeHelper.ts +162 -0
- package/src/data/table/helpers/selection/getMultipleSelectProps.ts +57 -20
- package/src/data/table/helpers/selection/selection.types.ts +1 -0
- package/src/data/table/helpers/table-keyboard.ts +1 -2
- package/src/data/table/hooks/__tests__/useTableItems.test.ts +14 -0
- package/src/data/table/hooks/__tests__/useTableSelection.test.ts +132 -21
- package/src/data/table/hooks/useTableExpansion.tsx +8 -3
- package/src/data/table/hooks/useTableItems.ts +50 -27
- package/src/data/table/hooks/useTableSelection.ts +10 -6
- package/src/data/table/root/DataTable.types.ts +5 -4
- package/src/data/table/root/DataTableAuto.test.tsx +128 -2
- package/src/data/table/root/DataTableAuto.tsx +144 -135
- package/src/data/table/root/DataTableRoot.tsx +6 -7
- package/src/form/checkbox/Checkbox.tsx +1 -0
- package/src/form/radio/Radio.tsx +7 -1
- package/src/modal/types.ts +8 -4
- package/src/data/table/hooks/__tests__/useTableExpansion.test.tsx +0 -115
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataTableRoot.js","sourceRoot":"","sources":["../../../../src/data/table/root/DataTableRoot.tsx"],"names":[],"mappings":";;;;;;;;;;;AAAA,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAChD,OAAO,EAAE,EAAE,EAAE,MAAM,wBAAwB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EACL,gBAAgB,GAEjB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,mBAAmB,GAEpB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"DataTableRoot.js","sourceRoot":"","sources":["../../../../src/data/table/root/DataTableRoot.tsx"],"names":[],"mappings":"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"}
|
|
@@ -23,6 +23,7 @@ export const Checkbox = forwardRef((props, forwardedRef) => {
|
|
|
23
23
|
"indeterminate",
|
|
24
24
|
"errorId",
|
|
25
25
|
"readOnly",
|
|
26
|
+
"className",
|
|
26
27
|
]), omit(inputProps, ["aria-invalid", "aria-describedby"]), { "aria-describedby": cl(inputProps["aria-describedby"], {
|
|
27
28
|
[descriptionId]: description,
|
|
28
29
|
}) || undefined, indeterminate: indeterminate !== null && indeterminate !== void 0 ? indeterminate : false, standalone: false })),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Checkbox.js","sourceRoot":"","sources":["../../../src/form/checkbox/Checkbox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,EAAE,EAAE,MAAM,qBAAqB,CAAC;AACzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAE/D,OAAO,WAAW,MAAM,eAAe,CAAC;AAExC,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAChC,CAAC,KAAoB,EAAE,YAAY,EAAE,EAAE;IACrC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAC5E,MAAM,aAAa,GAAG,KAAK,EAAE,CAAC;IAE9B,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,GAClE,KAAK,CAAC;IAER,OAAO,CACL,6BACE,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE,gBAAgB,EAAE,mBAAmB,IAAI,EAAE,EAAE;YACpE,uBAAuB,EAAE,QAAQ;YACjC,0BAA0B,EAAE,UAAU,CAAC,QAAQ;YAC/C,0BAA0B,EAAE,QAAQ;SACrC,CAAC,gBACU,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC;QAErD,oBAAC,aAAa,kBACZ,GAAG,EAAE,YAAY,IACb,IAAI,CAAC,KAAK,EAAE;YACd,UAAU;YACV,MAAM;YACN,OAAO;YACP,aAAa;YACb,WAAW;YACX,eAAe;YACf,SAAS;YACT,UAAU;
|
|
1
|
+
{"version":3,"file":"Checkbox.js","sourceRoot":"","sources":["../../../src/form/checkbox/Checkbox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,EAAE,EAAE,MAAM,qBAAqB,CAAC;AACzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAE/D,OAAO,WAAW,MAAM,eAAe,CAAC;AAExC,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAChC,CAAC,KAAoB,EAAE,YAAY,EAAE,EAAE;IACrC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAC5E,MAAM,aAAa,GAAG,KAAK,EAAE,CAAC;IAE9B,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,GAClE,KAAK,CAAC;IAER,OAAO,CACL,6BACE,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE,gBAAgB,EAAE,mBAAmB,IAAI,EAAE,EAAE;YACpE,uBAAuB,EAAE,QAAQ;YACjC,0BAA0B,EAAE,UAAU,CAAC,QAAQ;YAC/C,0BAA0B,EAAE,QAAQ;SACrC,CAAC,gBACU,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC;QAErD,oBAAC,aAAa,kBACZ,GAAG,EAAE,YAAY,IACb,IAAI,CAAC,KAAK,EAAE;YACd,UAAU;YACV,MAAM;YACN,OAAO;YACP,aAAa;YACb,WAAW;YACX,eAAe;YACf,SAAS;YACT,UAAU;YACV,WAAW;SACZ,CAAC,EACE,IAAI,CAAC,UAAU,EAAE,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC,wBAExD,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE;gBACjC,CAAC,aAAa,CAAC,EAAE,WAAW;aAC7B,CAAC,IAAI,SAAS,EAEjB,aAAa,EAAE,aAAa,aAAb,aAAa,cAAb,aAAa,GAAI,KAAK,EACrC,UAAU,EAAE,KAAK,IACjB;QACF,oBAAC,SAAS,IACR,EAAE,EAAC,OAAO,EACV,OAAO,EAAE,UAAU,CAAC,EAAE,EACtB,IAAI,EAAE,IAAI,EACV,SAAS,EAAC,uBAAuB,EACjC,cAAc,EAAE,SAAS;YAExB,CAAC,MAAM,IAAI,QAAQ,IAAI,oBAAC,qBAAqB,OAAG;YAChD,QAAQ,CACC;QACX,WAAW,IAAI,CACd,oBAAC,SAAS,IACR,EAAE,EAAE,aAAa,EACjB,IAAI,EAAE,IAAI,EACV,SAAS,EAAC,8DAA8D,EACxE,cAAc,EAAE,SAAS,IAExB,WAAW,CACF,CACb,CACG,CACP,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,eAAe,QAAQ,CAAC"}
|
package/esm/form/radio/Radio.js
CHANGED
|
@@ -13,7 +13,13 @@ export const Radio = forwardRef((props, forwardedRef) => {
|
|
|
13
13
|
"aksel-radio--disabled": inputProps.disabled,
|
|
14
14
|
"aksel-radio--readonly": readOnly,
|
|
15
15
|
}), "data-color": hasError ? "danger" : props["data-color"] },
|
|
16
|
-
React.createElement(RadioInput, Object.assign({ ref: forwardedRef }, omit(props, [
|
|
16
|
+
React.createElement(RadioInput, Object.assign({ ref: forwardedRef }, omit(props, [
|
|
17
|
+
"children",
|
|
18
|
+
"size",
|
|
19
|
+
"description",
|
|
20
|
+
"readOnly",
|
|
21
|
+
"className",
|
|
22
|
+
]), omit(inputProps, ["aria-invalid", "aria-describedby"]), { "aria-describedby": cl(inputProps["aria-describedby"], {
|
|
17
23
|
[descriptionId]: description,
|
|
18
24
|
}) || undefined, standalone: false })),
|
|
19
25
|
React.createElement(BodyShort, { as: "label", htmlFor: inputProps.id, className: "aksel-radio__label", size: size }, children),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Radio.js","sourceRoot":"","sources":["../../../src/form/radio/Radio.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,EAAE,EAAE,MAAM,qBAAqB,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAC7B,CAAC,KAAiB,EAAE,YAAY,EAAE,EAAE;IAClC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACjE,MAAM,aAAa,GAAG,KAAK,EAAE,CAAC;IAE9B,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAEnD,OAAO,CACL,6BACE,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE,aAAa,EAAE,gBAAgB,IAAI,EAAE,EAAE;YAC9D,oBAAoB,EAAE,QAAQ;YAC9B,uBAAuB,EAAE,UAAU,CAAC,QAAQ;YAC5C,uBAAuB,EAAE,QAAQ;SAClC,CAAC,gBACU,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC;QAErD,oBAAC,UAAU,kBACT,GAAG,EAAE,YAAY,IACb,IAAI,CAAC,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"Radio.js","sourceRoot":"","sources":["../../../src/form/radio/Radio.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,EAAE,EAAE,MAAM,qBAAqB,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAC7B,CAAC,KAAiB,EAAE,YAAY,EAAE,EAAE;IAClC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACjE,MAAM,aAAa,GAAG,KAAK,EAAE,CAAC;IAE9B,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAEnD,OAAO,CACL,6BACE,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE,aAAa,EAAE,gBAAgB,IAAI,EAAE,EAAE;YAC9D,oBAAoB,EAAE,QAAQ;YAC9B,uBAAuB,EAAE,UAAU,CAAC,QAAQ;YAC5C,uBAAuB,EAAE,QAAQ;SAClC,CAAC,gBACU,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC;QAErD,oBAAC,UAAU,kBACT,GAAG,EAAE,YAAY,IACb,IAAI,CAAC,KAAK,EAAE;YACd,UAAU;YACV,MAAM;YACN,aAAa;YACb,UAAU;YACV,WAAW;SACZ,CAAC,EACE,IAAI,CAAC,UAAU,EAAE,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC,wBAExD,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE;gBACjC,CAAC,aAAa,CAAC,EAAE,WAAW;aAC7B,CAAC,IAAI,SAAS,EAEjB,UAAU,EAAE,KAAK,IACjB;QACF,oBAAC,SAAS,IACR,EAAE,EAAC,OAAO,EACV,OAAO,EAAE,UAAU,CAAC,EAAE,EACtB,SAAS,EAAC,oBAAoB,EAC9B,IAAI,EAAE,IAAI,IAET,QAAQ,CACC;QACX,WAAW,IAAI,CACd,oBAAC,SAAS,IACR,EAAE,EAAE,aAAa,EACjB,IAAI,EAAE,IAAI,EACV,SAAS,EAAC,2DAA2D,IAEpE,WAAW,CACF,CACb,CACG,CACP,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,eAAe,KAAK,CAAC"}
|
package/esm/modal/types.d.ts
CHANGED
|
@@ -39,7 +39,8 @@ interface ModalPropsBase extends React.DialogHTMLAttributes<HTMLDialogElement> {
|
|
|
39
39
|
/**
|
|
40
40
|
* Called when the user tries to close the modal by one of the built-in methods.
|
|
41
41
|
* Used if you want to ask the user for confirmation before closing.
|
|
42
|
-
*
|
|
42
|
+
*
|
|
43
|
+
* **NB:** Will not always be called when pressing Esc. See `onCancel` for more info.
|
|
43
44
|
* @returns Whether to close the modal or not
|
|
44
45
|
*/
|
|
45
46
|
onBeforeClose?: () => boolean;
|
|
@@ -52,7 +53,8 @@ interface ModalPropsBase extends React.DialogHTMLAttributes<HTMLDialogElement> {
|
|
|
52
53
|
onCancel?: React.ReactEventHandler<HTMLDialogElement>;
|
|
53
54
|
/**
|
|
54
55
|
* Whether to close when clicking on the backdrop.
|
|
55
|
-
*
|
|
56
|
+
*
|
|
57
|
+
* **NB:** Users may click outside by accident. Don't use if closing can cause data loss, or the modal contains important info.
|
|
56
58
|
* @default false
|
|
57
59
|
*/
|
|
58
60
|
closeOnBackdropClick?: boolean;
|
|
@@ -77,13 +79,15 @@ interface ModalPropsBase extends React.DialogHTMLAttributes<HTMLDialogElement> {
|
|
|
77
79
|
/**
|
|
78
80
|
* ID of the element that labels the modal.
|
|
79
81
|
* No need to set this manually if the `header` prop is used. A reference to `header.heading` will be created automatically.
|
|
80
|
-
*
|
|
82
|
+
*
|
|
83
|
+
* **NB:** If not using `header`, you should set either `aria-labelledby` or `aria-label`.
|
|
81
84
|
*/
|
|
82
85
|
"aria-labelledby"?: string;
|
|
83
86
|
/**
|
|
84
87
|
* String value that labels the modal.
|
|
85
88
|
* No need to set this if the `header` prop is used.
|
|
86
|
-
*
|
|
89
|
+
*
|
|
90
|
+
* **NB:** If not using `header`, you should set either `aria-labelledby` or `aria-label`.
|
|
87
91
|
*/
|
|
88
92
|
"aria-label"?: string;
|
|
89
93
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@navikt/ds-react",
|
|
3
|
-
"version": "8.10.
|
|
3
|
+
"version": "8.10.3",
|
|
4
4
|
"description": "React components from the Norwegian Labour and Welfare Administration.",
|
|
5
5
|
"author": "Aksel, a team part of the Norwegian Labour and Welfare Administration.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -706,8 +706,8 @@
|
|
|
706
706
|
"dependencies": {
|
|
707
707
|
"@floating-ui/react": "0.27.8",
|
|
708
708
|
"@floating-ui/react-dom": "^2.1.8",
|
|
709
|
-
"@navikt/aksel-icons": "^8.10.
|
|
710
|
-
"@navikt/ds-tokens": "^8.10.
|
|
709
|
+
"@navikt/aksel-icons": "^8.10.3",
|
|
710
|
+
"@navikt/ds-tokens": "^8.10.3",
|
|
711
711
|
"date-fns": "^4.0.0",
|
|
712
712
|
"react-day-picker": "9.14.0"
|
|
713
713
|
},
|
|
@@ -62,12 +62,14 @@ const DataTableColumnHeader = forwardRef<
|
|
|
62
62
|
sortable = false,
|
|
63
63
|
sortDirection = "none",
|
|
64
64
|
onSortClick,
|
|
65
|
+
resizable = true,
|
|
65
66
|
style,
|
|
66
67
|
width,
|
|
68
|
+
defaultWidth,
|
|
69
|
+
autoWidth,
|
|
67
70
|
minWidth,
|
|
68
71
|
maxWidth,
|
|
69
72
|
onWidthChange,
|
|
70
|
-
defaultWidth,
|
|
71
73
|
colSpan,
|
|
72
74
|
rowSpan,
|
|
73
75
|
UNSAFE_isSelection,
|
|
@@ -80,9 +82,11 @@ const DataTableColumnHeader = forwardRef<
|
|
|
80
82
|
const mergedRef = useMergeRefs(forwardedRef, thRef);
|
|
81
83
|
|
|
82
84
|
const resizeResult = useTableColumnResize({
|
|
85
|
+
resizable,
|
|
83
86
|
thRef,
|
|
84
87
|
width,
|
|
85
88
|
defaultWidth,
|
|
89
|
+
autoWidth,
|
|
86
90
|
minWidth,
|
|
87
91
|
maxWidth,
|
|
88
92
|
onWidthChange,
|
|
@@ -1,10 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type DOMAttributes,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from "react";
|
|
2
8
|
import { useControllableState } from "../../../utils/hooks";
|
|
3
9
|
import { useDataTableContext } from "../root/DataTableRoot.context";
|
|
4
10
|
|
|
5
11
|
type ColumnWidth = number | string;
|
|
6
12
|
|
|
7
13
|
type ResizeProps = {
|
|
14
|
+
/**
|
|
15
|
+
* Whether the column should be resizable by the user.
|
|
16
|
+
*
|
|
17
|
+
* **NB:** This is always disabled when `layout="auto"` on the root component.
|
|
18
|
+
* @default true
|
|
19
|
+
*/
|
|
20
|
+
resizable?: boolean;
|
|
8
21
|
/**
|
|
9
22
|
* Controlled width of the column.
|
|
10
23
|
*
|
|
@@ -12,9 +25,20 @@ type ResizeProps = {
|
|
|
12
25
|
*/
|
|
13
26
|
width?: ColumnWidth;
|
|
14
27
|
/**
|
|
15
|
-
* Initial width of the column. Only used when `width` is not set.
|
|
28
|
+
* Initial width of the column. Only used when `width` is not set and `resizable` is true.
|
|
16
29
|
*/
|
|
17
30
|
defaultWidth?: ColumnWidth;
|
|
31
|
+
/**
|
|
32
|
+
* Whether the column should automatically resize to fit its content. **Runs only once.**
|
|
33
|
+
*
|
|
34
|
+
* `onWidthChange` will be called with the new size. `minWidth` and `maxWidth` will be respected.
|
|
35
|
+
*
|
|
36
|
+
* If you don't need manual resizing support and want most of the columns to resize automatically,
|
|
37
|
+
* consider using `layout="auto"` on the root instead for better performance.
|
|
38
|
+
*
|
|
39
|
+
* **NB:** This can cause a layout shift. Set a good initial width with `width` or `defaultWidth` to mitigate this.
|
|
40
|
+
*/
|
|
41
|
+
autoWidth?: boolean;
|
|
18
42
|
/**
|
|
19
43
|
* Minimum width of the column.
|
|
20
44
|
*
|
|
@@ -41,7 +65,12 @@ type ResizeProps = {
|
|
|
41
65
|
colSpan?: number;
|
|
42
66
|
};
|
|
43
67
|
|
|
44
|
-
type
|
|
68
|
+
type WithUndefined<T> = {
|
|
69
|
+
[K in keyof T]: T[K] | undefined;
|
|
70
|
+
};
|
|
71
|
+
type Unomittable<T> = WithUndefined<Required<T>>;
|
|
72
|
+
|
|
73
|
+
type TableColumnResizeArgs = Unomittable<ResizeProps> & {
|
|
45
74
|
thRef: React.RefObject<HTMLTableCellElement | null>;
|
|
46
75
|
};
|
|
47
76
|
|
|
@@ -74,9 +103,11 @@ function useTableColumnResize(
|
|
|
74
103
|
args: TableColumnResizeArgs,
|
|
75
104
|
): TableColumnResizeResult {
|
|
76
105
|
const {
|
|
106
|
+
resizable,
|
|
77
107
|
thRef,
|
|
78
108
|
width: userWidth,
|
|
79
109
|
defaultWidth,
|
|
110
|
+
autoWidth,
|
|
80
111
|
onWidthChange,
|
|
81
112
|
maxWidth = Infinity,
|
|
82
113
|
minWidth = 40,
|
|
@@ -89,7 +120,7 @@ function useTableColumnResize(
|
|
|
89
120
|
const [isResizingWithKeyboard, setIsResizingWithKeyboard] = useState(false);
|
|
90
121
|
const ignoreNextOnClick = useRef(false);
|
|
91
122
|
|
|
92
|
-
const [width,
|
|
123
|
+
const [width, setWidth] = useControllableState({
|
|
93
124
|
value: userWidth,
|
|
94
125
|
defaultValue: defaultWidth ?? (colSpan ?? 1) * 140,
|
|
95
126
|
/**
|
|
@@ -100,14 +131,29 @@ function useTableColumnResize(
|
|
|
100
131
|
onChange: onWidthChange,
|
|
101
132
|
});
|
|
102
133
|
|
|
103
|
-
const
|
|
134
|
+
const setClampedWidth = useCallback(
|
|
104
135
|
(newWidth: number) => {
|
|
105
136
|
const min = parseWidth(minWidth) ?? 0;
|
|
106
137
|
const max = parseWidth(maxWidth) ?? Infinity;
|
|
107
138
|
const clamped = Math.min(Math.max(newWidth, min), max);
|
|
108
|
-
|
|
139
|
+
setWidth(clamped);
|
|
109
140
|
},
|
|
110
|
-
[minWidth, maxWidth,
|
|
141
|
+
[minWidth, maxWidth, setWidth],
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: We only want to run this on mount and when autoWidth changes
|
|
145
|
+
useEffect(
|
|
146
|
+
function autoResizeColumn() {
|
|
147
|
+
if (!autoWidth) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const newColumnWidth = getAutoColumnWidth(thRef);
|
|
152
|
+
if (newColumnWidth) {
|
|
153
|
+
setClampedWidth(newColumnWidth);
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
[autoWidth], // eslint-disable-line react-hooks/exhaustive-deps
|
|
111
157
|
);
|
|
112
158
|
|
|
113
159
|
const handleOnClick: DOMAttributes<HTMLButtonElement>["onClick"] =
|
|
@@ -135,19 +181,19 @@ function useTableColumnResize(
|
|
|
135
181
|
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
|
136
182
|
event.preventDefault();
|
|
137
183
|
const delta = event.key === "ArrowRight" ? 20 : -20;
|
|
138
|
-
|
|
184
|
+
setClampedWidth(currentWidth + delta);
|
|
139
185
|
return;
|
|
140
186
|
}
|
|
141
187
|
if (event.key === "Home") {
|
|
142
188
|
event.preventDefault();
|
|
143
|
-
|
|
189
|
+
setClampedWidth(0); // will fall back to minWidth
|
|
144
190
|
return;
|
|
145
191
|
}
|
|
146
192
|
if (event.key === "End") {
|
|
147
193
|
event.preventDefault();
|
|
148
|
-
const
|
|
149
|
-
if (
|
|
150
|
-
|
|
194
|
+
const newWidth = getAutoColumnWidth(thRef);
|
|
195
|
+
if (newWidth && newWidth > currentWidth) {
|
|
196
|
+
setClampedWidth(newWidth);
|
|
151
197
|
}
|
|
152
198
|
return;
|
|
153
199
|
}
|
|
@@ -155,7 +201,7 @@ function useTableColumnResize(
|
|
|
155
201
|
setIsResizingWithKeyboard(false);
|
|
156
202
|
}
|
|
157
203
|
},
|
|
158
|
-
[isResizingWithKeyboard,
|
|
204
|
+
[isResizingWithKeyboard, setClampedWidth, thRef],
|
|
159
205
|
);
|
|
160
206
|
|
|
161
207
|
const startResize = useCallback(
|
|
@@ -178,7 +224,7 @@ function useTableColumnResize(
|
|
|
178
224
|
return;
|
|
179
225
|
}
|
|
180
226
|
|
|
181
|
-
|
|
227
|
+
setClampedWidth(newWidth);
|
|
182
228
|
}
|
|
183
229
|
|
|
184
230
|
function onMouseMove(e: MouseEvent) {
|
|
@@ -206,7 +252,7 @@ function useTableColumnResize(
|
|
|
206
252
|
document.addEventListener("touchend", cleanup, { once: true });
|
|
207
253
|
document.addEventListener("touchcancel", cleanup, { once: true });
|
|
208
254
|
},
|
|
209
|
-
[maxWidth, minWidth, setWidth, thRef],
|
|
255
|
+
[maxWidth, minWidth, setWidth, setClampedWidth, thRef],
|
|
210
256
|
);
|
|
211
257
|
|
|
212
258
|
const handleMouseDown: DOMAttributes<HTMLButtonElement>["onMouseDown"] =
|
|
@@ -230,9 +276,9 @@ function useTableColumnResize(
|
|
|
230
276
|
useCallback(() => {
|
|
231
277
|
const newColumnWidth = getAutoColumnWidth(thRef);
|
|
232
278
|
if (newColumnWidth) {
|
|
233
|
-
|
|
279
|
+
setClampedWidth(newColumnWidth);
|
|
234
280
|
}
|
|
235
|
-
}, [
|
|
281
|
+
}, [setClampedWidth, thRef]);
|
|
236
282
|
|
|
237
283
|
if (tableContext.layout !== "fixed") {
|
|
238
284
|
return {
|
|
@@ -241,6 +287,16 @@ function useTableColumnResize(
|
|
|
241
287
|
};
|
|
242
288
|
}
|
|
243
289
|
|
|
290
|
+
if (!resizable) {
|
|
291
|
+
return {
|
|
292
|
+
style: {
|
|
293
|
+
...style,
|
|
294
|
+
width,
|
|
295
|
+
},
|
|
296
|
+
enabled: false,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
244
300
|
return {
|
|
245
301
|
style: {
|
|
246
302
|
...style,
|
|
@@ -14,45 +14,77 @@ interface ItemDetail<T> {
|
|
|
14
14
|
children: readonly T[];
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
type CollectTableRowEntriesReturn<T> = {
|
|
18
|
+
itemDetails: Map<T, ItemDetail<T>>;
|
|
19
|
+
/**
|
|
20
|
+
* Direct child ids for each row, used to traverse nested selection groups
|
|
21
|
+
* without storing every descendant list on each ancestor.
|
|
22
|
+
*/
|
|
23
|
+
childRowIdsById: Map<TableRowEntryId, TableRowEntryId[]>;
|
|
24
|
+
};
|
|
25
|
+
|
|
17
26
|
function collectTableRowEntries<T>({
|
|
18
27
|
items,
|
|
19
28
|
getRowId,
|
|
20
29
|
getSubRows,
|
|
21
30
|
isSubRowExpandable,
|
|
22
|
-
}: CollectTableRowEntriesArgs<T>):
|
|
31
|
+
}: CollectTableRowEntriesArgs<T>): CollectTableRowEntriesReturn<T> {
|
|
23
32
|
const itemDetailsMap = new Map<T, ItemDetail<T>>();
|
|
33
|
+
const childRowIdsById = new Map<TableRowEntryId, TableRowEntryId[]>();
|
|
24
34
|
|
|
25
|
-
const
|
|
26
|
-
|
|
35
|
+
const traverseRow = (
|
|
36
|
+
rowData: T,
|
|
37
|
+
rowIndex: number,
|
|
27
38
|
level: number,
|
|
28
39
|
parent: T | null,
|
|
29
40
|
parentId?: TableRowEntryId,
|
|
30
|
-
) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
): TableRowEntryId => {
|
|
42
|
+
const rowId =
|
|
43
|
+
getRowId?.(rowData, rowIndex) ??
|
|
44
|
+
(parentId == null ? rowIndex : `${parentId}-${rowIndex}`);
|
|
45
|
+
const isRowExpandable = isSubRowExpandable?.(rowData) ?? true;
|
|
46
|
+
const children = (isRowExpandable ? getSubRows?.(rowData) : []) ?? [];
|
|
47
|
+
|
|
48
|
+
itemDetailsMap.set(rowData, {
|
|
49
|
+
id: rowId,
|
|
50
|
+
level,
|
|
51
|
+
parent,
|
|
52
|
+
children,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const childRowIds: TableRowEntryId[] = [];
|
|
56
|
+
|
|
57
|
+
for (let childIndex = 0; childIndex < children.length; childIndex++) {
|
|
58
|
+
const childRow = children[childIndex];
|
|
59
|
+
const childRowId = traverseRow(
|
|
60
|
+
childRow,
|
|
61
|
+
childIndex,
|
|
62
|
+
level + 1,
|
|
63
|
+
rowData,
|
|
64
|
+
rowId,
|
|
65
|
+
);
|
|
66
|
+
childRowIds.push(childRowId);
|
|
49
67
|
}
|
|
68
|
+
|
|
69
|
+
childRowIdsById.set(rowId, childRowIds);
|
|
70
|
+
|
|
71
|
+
return rowId;
|
|
50
72
|
};
|
|
51
73
|
|
|
52
|
-
|
|
74
|
+
for (let rowIndex = 0; rowIndex < items.length; rowIndex++) {
|
|
75
|
+
traverseRow(items[rowIndex], rowIndex, 0, null);
|
|
76
|
+
}
|
|
53
77
|
|
|
54
|
-
return
|
|
78
|
+
return {
|
|
79
|
+
itemDetails: itemDetailsMap,
|
|
80
|
+
childRowIdsById,
|
|
81
|
+
};
|
|
55
82
|
}
|
|
56
83
|
|
|
57
84
|
export { collectTableRowEntries };
|
|
58
|
-
export type {
|
|
85
|
+
export type {
|
|
86
|
+
CollectTableRowEntriesArgs,
|
|
87
|
+
CollectTableRowEntriesReturn,
|
|
88
|
+
TableRowEntryId,
|
|
89
|
+
ItemDetail,
|
|
90
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { SelectionSubtreeHelper } from "./SelectionSubtreeHelper";
|
|
3
|
+
|
|
4
|
+
const childRowIdsById = new Map<string | number, (string | number)[]>([
|
|
5
|
+
["a", ["a1", "a2"]],
|
|
6
|
+
["a1", []],
|
|
7
|
+
["a2", ["a2a"]],
|
|
8
|
+
["a2a", []],
|
|
9
|
+
["b", []],
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
describe("SelectionSubtreeHelper", () => {
|
|
13
|
+
test("returns selectable subtree keys without duplicates", () => {
|
|
14
|
+
const helper = new SelectionSubtreeHelper({
|
|
15
|
+
childRowIdsById,
|
|
16
|
+
disabledKeysSet: new Set(["a2"]),
|
|
17
|
+
selectedKeysSet: new Set(),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
expect(helper.getSelectableKeys(["a", "a1"])).toEqual(["a", "a1", "a2a"]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("returns cached subtree selection stats", () => {
|
|
24
|
+
const helper = new SelectionSubtreeHelper({
|
|
25
|
+
childRowIdsById,
|
|
26
|
+
disabledKeysSet: new Set(["a2"]),
|
|
27
|
+
selectedKeysSet: new Set(["a", "a1", "a2a"]),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const firstStats = helper.getSelectionStats("a");
|
|
31
|
+
const secondStats = helper.getSelectionStats("a");
|
|
32
|
+
|
|
33
|
+
expect(firstStats).toEqual({ selectableCount: 3, selectedCount: 3 });
|
|
34
|
+
expect(secondStats).toBe(firstStats);
|
|
35
|
+
expect(helper.isFullySelected("a")).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("handles deep trees iteratively", () => {
|
|
39
|
+
const depth = 12000;
|
|
40
|
+
const deepChildRowIdsById = new Map<string | number, (string | number)[]>();
|
|
41
|
+
const selectedKeysSet = new Set<string | number>();
|
|
42
|
+
|
|
43
|
+
for (let index = 0; index < depth; index++) {
|
|
44
|
+
const key = `node-${index}`;
|
|
45
|
+
const childKey = `node-${index + 1}`;
|
|
46
|
+
|
|
47
|
+
deepChildRowIdsById.set(key, index === depth - 1 ? [] : [childKey]);
|
|
48
|
+
|
|
49
|
+
if (index % 2 === 0) {
|
|
50
|
+
selectedKeysSet.add(key);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const helper = new SelectionSubtreeHelper({
|
|
55
|
+
childRowIdsById: deepChildRowIdsById,
|
|
56
|
+
disabledKeysSet: new Set(),
|
|
57
|
+
selectedKeysSet,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(helper.getSelectableKeys(["node-0"])).toHaveLength(depth);
|
|
61
|
+
expect(helper.getSelectionStats("node-0")).toEqual({
|
|
62
|
+
selectableCount: depth,
|
|
63
|
+
selectedCount: Math.ceil(depth / 2),
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
type SelectionKey = string | number;
|
|
2
|
+
|
|
3
|
+
type SelectionStats = {
|
|
4
|
+
selectableCount: number;
|
|
5
|
+
selectedCount: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type SelectionSubtreeHelperArgs = {
|
|
9
|
+
childRowIdsById?: Map<SelectionKey, SelectionKey[]>;
|
|
10
|
+
disabledKeysSet: Set<SelectionKey>;
|
|
11
|
+
selectedKeysSet: Set<SelectionKey>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Helper class for managing selection state in a tree structure.
|
|
16
|
+
* - It provides methods to get selectable keys in a subtree
|
|
17
|
+
* - Compute selection statistics for a subtree
|
|
18
|
+
* - Determine if a subtree is fully selected.
|
|
19
|
+
*
|
|
20
|
+
* Results of selection statistics are cached to optimize performance for repeated calls on the same subtree.
|
|
21
|
+
*/
|
|
22
|
+
class SelectionSubtreeHelper {
|
|
23
|
+
private childRowIdsById: Map<SelectionKey, SelectionKey[]>;
|
|
24
|
+
private disabledKeysSet: Set<SelectionKey>;
|
|
25
|
+
private selectedKeysSet: Set<SelectionKey>;
|
|
26
|
+
private selectionStatsCache = new Map<SelectionKey, SelectionStats>();
|
|
27
|
+
|
|
28
|
+
constructor({
|
|
29
|
+
childRowIdsById,
|
|
30
|
+
disabledKeysSet,
|
|
31
|
+
selectedKeysSet,
|
|
32
|
+
}: SelectionSubtreeHelperArgs) {
|
|
33
|
+
this.childRowIdsById = childRowIdsById ?? new Map();
|
|
34
|
+
this.disabledKeysSet = disabledKeysSet;
|
|
35
|
+
this.selectedKeysSet = selectedKeysSet;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getSelectableKeys(rootIds: SelectionKey[]): SelectionKey[] {
|
|
39
|
+
const visitedKeys = new Set<SelectionKey>();
|
|
40
|
+
const selectableKeys: SelectionKey[] = [];
|
|
41
|
+
const stack = [...rootIds].reverse();
|
|
42
|
+
|
|
43
|
+
while (stack.length > 0) {
|
|
44
|
+
const key = stack.pop();
|
|
45
|
+
|
|
46
|
+
if (key == null || visitedKeys.has(key)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
visitedKeys.add(key);
|
|
51
|
+
|
|
52
|
+
if (!this.disabledKeysSet.has(key)) {
|
|
53
|
+
selectableKeys.push(key);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const childRowIds = this.childRowIdsById.get(key) ?? [];
|
|
57
|
+
|
|
58
|
+
for (
|
|
59
|
+
let childIndex = childRowIds.length - 1;
|
|
60
|
+
childIndex >= 0;
|
|
61
|
+
childIndex--
|
|
62
|
+
) {
|
|
63
|
+
stack.push(childRowIds[childIndex]);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return selectableKeys;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Returns the number of selectable and selected rows in the subtree of the given root ID.
|
|
72
|
+
* Results are cached after the first computation to optimize repeated calls for the same root ID.
|
|
73
|
+
*
|
|
74
|
+
* The selectable count excludes disabled rows, and the selected count excludes disabled rows that are selected.
|
|
75
|
+
* The method is implemented iteratively to handle deep trees without hitting call stack limits.
|
|
76
|
+
*
|
|
77
|
+
* How it works:
|
|
78
|
+
* - Manually add root ID to stack to get processing going. Note that the ready-flag is `false`.
|
|
79
|
+
* - Pop stack until empty. For each entry:
|
|
80
|
+
* - - If entry is already cached, skip it.
|
|
81
|
+
* - - If entry is not ready, push it back as ready and push all its children as not ready.
|
|
82
|
+
* - - If entry is ready, compute its stats based on its own state and the stats of its children, then cache the result.
|
|
83
|
+
* - Since we add all the children to the stack after pushing element with ready: true, while "popping" the stack we will always encounter the children before their parent is ready, ensuring that the stats for all children are computed and cached before computing the stats for their parent.
|
|
84
|
+
* - Finally, return the cached stats for the root ID.
|
|
85
|
+
*/
|
|
86
|
+
getSelectionStats(rootId: SelectionKey): SelectionStats {
|
|
87
|
+
const cachedStats = this.selectionStatsCache.get(rootId);
|
|
88
|
+
|
|
89
|
+
if (cachedStats) {
|
|
90
|
+
return cachedStats;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Compute subtree totals iteratively so deep trees do not depend on call stack depth. */
|
|
94
|
+
const stack: { key: SelectionKey; ready: boolean }[] = [
|
|
95
|
+
{ key: rootId, ready: false },
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
while (stack.length > 0) {
|
|
99
|
+
const entry = stack.pop();
|
|
100
|
+
|
|
101
|
+
if (!entry) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (this.selectionStatsCache.has(entry.key)) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (entry.ready) {
|
|
110
|
+
let selectableCount = this.disabledKeysSet.has(entry.key) ? 0 : 1;
|
|
111
|
+
let selectedCount =
|
|
112
|
+
!this.disabledKeysSet.has(entry.key) &&
|
|
113
|
+
this.selectedKeysSet.has(entry.key)
|
|
114
|
+
? 1
|
|
115
|
+
: 0;
|
|
116
|
+
|
|
117
|
+
for (const childKey of this.childRowIdsById.get(entry.key) ?? []) {
|
|
118
|
+
const childStats = this.selectionStatsCache.get(childKey);
|
|
119
|
+
|
|
120
|
+
if (!childStats) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
selectableCount += childStats.selectableCount;
|
|
125
|
+
selectedCount += childStats.selectedCount;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.selectionStatsCache.set(entry.key, {
|
|
129
|
+
selectableCount,
|
|
130
|
+
selectedCount,
|
|
131
|
+
});
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
stack.push({ key: entry.key, ready: true });
|
|
136
|
+
|
|
137
|
+
for (const childKey of this.childRowIdsById.get(entry.key) ?? []) {
|
|
138
|
+
if (!this.selectionStatsCache.has(childKey)) {
|
|
139
|
+
stack.push({ key: childKey, ready: false });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
this.selectionStatsCache.get(rootId) ?? {
|
|
146
|
+
selectableCount: 0,
|
|
147
|
+
selectedCount: 0,
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
isFullySelected(rootId: SelectionKey): boolean {
|
|
153
|
+
const stats = this.getSelectionStats(rootId);
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
stats.selectableCount > 0 && stats.selectedCount === stats.selectableCount
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export { SelectionSubtreeHelper };
|
|
162
|
+
export type { SelectionKey, SelectionStats, SelectionSubtreeHelperArgs };
|