@olenbetong/appframe-ds 1.3.6 → 1.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +6 -6
  3. package/scripts/copyAssets.mjs +27 -27
  4. package/src/AfLookup/Lookup.css +130 -129
  5. package/src/AfLookup/Lookup.tsx +186 -186
  6. package/src/AfLookup/LookupCombobox.css +72 -78
  7. package/src/AfLookup/LookupCombobox.tsx +237 -237
  8. package/src/AfLookup/LookupEdit.tsx +130 -130
  9. package/src/AfLookup/LookupGrid.css +7 -12
  10. package/src/AfLookup/LookupGrid.tsx +124 -124
  11. package/src/AfLookup/index.ts +5 -5
  12. package/src/autocomplete/Autocomplete.css +256 -256
  13. package/src/autocomplete/Autocomplete.tsx +320 -320
  14. package/src/autocomplete/AutocompleteFrame.tsx +168 -168
  15. package/src/autocomplete/Combobox.tsx +286 -286
  16. package/src/autocomplete/index.ts +4 -4
  17. package/src/autocomplete/types.ts +204 -204
  18. package/src/autocomplete/utils.ts +183 -183
  19. package/src/binding/BoundNumericTextField.tsx +102 -102
  20. package/src/binding/BoundTextField.tsx +49 -49
  21. package/src/binding/CancelButton.tsx +30 -30
  22. package/src/binding/CancelIconButton.tsx +30 -30
  23. package/src/binding/DataEditToolbar.css +23 -23
  24. package/src/binding/DataEditToolbar.tsx +84 -84
  25. package/src/binding/DeleteButton.tsx +35 -35
  26. package/src/binding/DeleteIconButton.tsx +35 -35
  27. package/src/binding/RefreshButton.tsx +31 -31
  28. package/src/binding/RefreshIconButton.tsx +33 -33
  29. package/src/binding/RefreshRowIconButton.tsx +33 -33
  30. package/src/binding/SaveButton.tsx +37 -37
  31. package/src/binding/SaveIconButton.tsx +38 -38
  32. package/src/container/Container.tsx +50 -49
  33. package/src/container/index.ts +1 -1
  34. package/src/cutoff/CutOffPicker.css +12 -12
  35. package/src/cutoff/CutOffPicker.tsx +165 -165
  36. package/src/cutoff/context.tsx +194 -194
  37. package/src/cutoff/dsCutOffDates.ts +21 -21
  38. package/src/cutoff/index.ts +4 -4
  39. package/src/cutoff/utils.test.ts +59 -59
  40. package/src/cutoff/utils.ts +276 -276
  41. package/src/filter/ChipListInput.tsx +79 -79
  42. package/src/filter/FieldFilterPanel.css +150 -150
  43. package/src/filter/FieldFilterPanel.tsx +309 -309
  44. package/src/filter/FilterBuilder.css +152 -152
  45. package/src/filter/FilterBuilder.test.tsx +153 -153
  46. package/src/filter/FilterBuilder.tsx +435 -435
  47. package/src/filter/FilterEditor.css +231 -231
  48. package/src/filter/FilterEditor.test.tsx +285 -285
  49. package/src/filter/FilterEditor.tsx +75 -75
  50. package/src/filter/FilterGroupEditor.tsx +160 -160
  51. package/src/filter/FilterNameDialog.css +22 -22
  52. package/src/filter/FilterNameDialog.tsx +81 -81
  53. package/src/filter/FilterRow.tsx +117 -117
  54. package/src/filter/FilterShareDialog.css +28 -28
  55. package/src/filter/FilterShareDialog.tsx +201 -201
  56. package/src/filter/FilterStringField.tsx +78 -78
  57. package/src/filter/FilterValueEditor.tsx +239 -239
  58. package/src/filter/SavedFilterTree.css +107 -107
  59. package/src/filter/SavedFilterTree.test.tsx +94 -94
  60. package/src/filter/SavedFilterTree.tsx +218 -218
  61. package/src/filter/fieldFilter.test.ts +158 -158
  62. package/src/filter/fieldFilter.ts +139 -139
  63. package/src/filter/index.ts +27 -27
  64. package/src/filter/types.ts +38 -38
  65. package/src/filter/useDistinctValues.test.ts +102 -102
  66. package/src/filter/useDistinctValues.ts +230 -230
  67. package/src/global.d.ts +11 -11
  68. package/src/grid/AfGridColumnsPanel.tsx +118 -118
  69. package/src/grid/AfGridContext.tsx +51 -51
  70. package/src/grid/AfGridError.tsx +46 -46
  71. package/src/grid/AfHeaderFilterCell.tsx +147 -147
  72. package/src/grid/AfHeaderFilterPanel.tsx +111 -111
  73. package/src/grid/GridEditLookup.css +12 -12
  74. package/src/grid/GridEditLookup.tsx +121 -121
  75. package/src/grid/Toolbar.tsx +233 -233
  76. package/src/grid/editing.ts +100 -100
  77. package/src/grid/filter.ts +78 -78
  78. package/src/grid/formatters.ts +48 -48
  79. package/src/grid/index.css +245 -245
  80. package/src/grid/index.tsx +503 -503
  81. package/src/grid/license.ts +48 -48
  82. package/src/grid/localization.ts +369 -369
  83. package/src/grid/slots/index.tsx +334 -334
  84. package/src/grid/slots/slots.css +64 -64
  85. package/src/grid/slots/slots.test.tsx +39 -39
  86. package/src/grid/theme.tsx +138 -138
  87. package/src/grid/useAfColumns.tsx +371 -371
  88. package/src/grid/useAfCurrentIndex.ts +63 -63
  89. package/src/grid/useAfData.ts +20 -20
  90. package/src/grid/useAfFilter.ts +160 -160
  91. package/src/grid/useAfFilterFields.ts +55 -55
  92. package/src/grid/useAfGridApi.ts +71 -71
  93. package/src/grid/useAfKeyBindings.ts +36 -36
  94. package/src/grid/useAfNewItemRow.ts +258 -258
  95. package/src/grid/useAfPagination.ts +53 -53
  96. package/src/grid/useAfPersistedState.ts +230 -230
  97. package/src/grid/useAfRowEditModel.ts +145 -145
  98. package/src/grid/useAfRowGrouping.ts +47 -47
  99. package/src/grid/useAfServerAggregation.test.ts +68 -68
  100. package/src/grid/useAfServerAggregation.ts +158 -158
  101. package/src/grid/useAfSortModel.ts +84 -84
  102. package/src/i18n.ts +13 -13
  103. package/src/index.ts +14 -14
  104. package/src/input/InputAdornments.css +84 -84
  105. package/src/input/InputAdornments.tsx +102 -102
  106. package/src/input/index.ts +1 -1
  107. package/src/layout/AppMenu.tsx +74 -74
  108. package/src/layout/AppMenuDrawer.css +70 -70
  109. package/src/layout/AppMenuDrawer.tsx +122 -122
  110. package/src/layout/ErrorBoundary.tsx +162 -162
  111. package/src/layout/PageContainer.css +48 -48
  112. package/src/layout/PageContainer.tsx +41 -41
  113. package/src/layout/ResponsiveDialog.css +86 -86
  114. package/src/layout/ResponsiveDialog.tsx +55 -55
  115. package/src/layout/index.tsx +69 -69
  116. package/src/link/Link.tsx +23 -23
  117. package/src/link/index.ts +1 -1
  118. package/src/mdi/Mdi.css +138 -138
  119. package/src/mdi/Mdi.test.tsx +91 -91
  120. package/src/mdi/Mdi.tsx +141 -141
  121. package/src/mdi/MdiConfirmCloseDialog.tsx +47 -47
  122. package/src/mdi/MdiContext.tsx +59 -59
  123. package/src/mdi/MdiDocumentContext.tsx +60 -60
  124. package/src/mdi/MdiPanel.tsx +61 -61
  125. package/src/mdi/MdiRestore.test.tsx +86 -86
  126. package/src/mdi/MdiTab.tsx +30 -30
  127. package/src/mdi/controller.test.ts +382 -382
  128. package/src/mdi/controller.ts +656 -656
  129. package/src/mdi/dockviewHost.ts +132 -132
  130. package/src/mdi/fakeHost.ts +200 -200
  131. package/src/mdi/host.ts +44 -44
  132. package/src/mdi/ids.test.ts +20 -20
  133. package/src/mdi/ids.ts +18 -18
  134. package/src/mdi/index.ts +31 -31
  135. package/src/mdi/layoutRestore.test.ts +156 -156
  136. package/src/mdi/layoutRestore.ts +311 -311
  137. package/src/mdi/layoutSpec.test.ts +143 -143
  138. package/src/mdi/layoutSpec.ts +214 -214
  139. package/src/mdi/layoutStore.test.ts +93 -93
  140. package/src/mdi/layoutStore.ts +99 -99
  141. package/src/mdi/memorySettingsStore.ts +21 -21
  142. package/src/mdi/registry.ts +34 -34
  143. package/src/mdi/testTypes.ts +37 -37
  144. package/src/mdi/theme.ts +22 -22
  145. package/src/mdi/types.ts +214 -214
  146. package/src/page/Page.css +53 -53
  147. package/src/page/PageBlock.css +64 -64
  148. package/src/page/PageBlock.tsx +93 -92
  149. package/src/page/PageHeader.css +88 -88
  150. package/src/page/PageHeader.tsx +115 -115
  151. package/src/page/PageTitle.css +5 -5
  152. package/src/page/PageTitle.tsx +21 -21
  153. package/src/page/index.ts +4 -4
  154. package/src/paper/Paper.tsx +52 -52
  155. package/src/progress/LinearProgress.css +93 -93
  156. package/src/progress/LinearProgress.tsx +104 -104
  157. package/src/progress/index.ts +1 -1
  158. package/src/report-downloader/ReportDownloader.css +67 -67
  159. package/src/report-downloader/components/StatusItem.tsx +87 -87
  160. package/src/report-downloader/components/StatusList.tsx +28 -28
  161. package/src/report-downloader/index.ts +41 -41
  162. package/src/report-downloader/status.tsx +92 -92
  163. package/src/sortable/DropIndicator.css +51 -51
  164. package/src/sortable/DropIndicator.tsx +27 -27
  165. package/src/sortable/SortableItemList.css +21 -21
  166. package/src/sortable/SortableItemList.tsx +264 -264
  167. package/src/sortable/index.ts +8 -8
  168. package/src/sortable/reorderSortOrder.ts +126 -126
  169. package/src/sortable/sortOrder.ts +101 -101
  170. package/src/sortable/types.ts +9 -9
  171. package/src/sortable/useSortableContainer.ts +53 -53
  172. package/src/sortable/useSortableItem.ts +95 -95
  173. package/src/sortable/useSortableList.ts +117 -117
  174. package/src/test/happy-dom-document-class.ts +24 -24
  175. package/src/test/setup.ts +12 -12
  176. package/src/theme/useDsColorScheme.ts +53 -53
  177. package/tsconfig.build.json +9 -9
  178. package/tsconfig.json +8 -8
  179. package/vitest.config.ts +10 -10
@@ -1,95 +1,95 @@
1
- import { combine } from "@atlaskit/pragmatic-drag-and-drop/utils/combine";
2
- import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/adapter/element-adapter";
3
- import {
4
- attachClosestEdge,
5
- type Edge,
6
- extractClosestEdge,
7
- } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
8
- import { type RefObject, useEffect, useState } from "react";
9
-
10
- import { isSortableDragData, type SortableDragData } from "./useSortableContainer.js";
11
- import type { SortableRecord } from "./types.js";
12
-
13
- export type SortableItemState =
14
- | { type: "idle" }
15
- | { type: "is-dragging" }
16
- | { type: "is-dragging-over"; edge: Edge | null };
17
-
18
- export interface UseSortableItemOptions<T extends SortableRecord> {
19
- /** Identifies the list. Must match the type given to the container/list */
20
- type: string;
21
- record: T;
22
- /** Element that acts as the drop target, usually the list item */
23
- itemRef: RefObject<HTMLElement | null>;
24
- /** Element that starts the drag, usually a drag handle button */
25
- handleRef: RefObject<HTMLElement | null>;
26
- enabled?: boolean;
27
- }
28
-
29
- /**
30
- * Makes a single list item draggable and turns it into a drop target that
31
- * reports which edge is closest to the pointer, so a drop indicator can be
32
- * rendered.
33
- */
34
- export function useSortableItem<T extends SortableRecord>({
35
- type,
36
- record,
37
- itemRef,
38
- handleRef,
39
- enabled = true,
40
- }: UseSortableItemOptions<T>): SortableItemState {
41
- let [state, setState] = useState<SortableItemState>({ type: "idle" });
42
-
43
- useEffect(() => {
44
- if (!enabled) return;
45
- let item = itemRef.current;
46
- let handle = handleRef.current;
47
- if (!item || !handle) return;
48
-
49
- return combine(
50
- draggable({
51
- element: handle,
52
- getInitialData: () => ({ type, record }) satisfies SortableDragData<T>,
53
- onDragStart: () => setState({ type: "is-dragging" }),
54
- onDrop: () => setState({ type: "idle" }),
55
- }),
56
- dropTargetForElements({
57
- element: item,
58
- canDrop({ source }) {
59
- if (!isSortableDragData<T>(source.data, type)) return false;
60
- return source.data.record.PrimKey !== record.PrimKey;
61
- },
62
- getData({ input }) {
63
- let data: SortableDragData<T> = { type, record };
64
- return attachClosestEdge(data, {
65
- element: item,
66
- input,
67
- allowedEdges: ["top", "bottom"],
68
- });
69
- },
70
- getIsSticky: () => true,
71
- onDragEnter({ self }) {
72
- let edge = extractClosestEdge(self.data);
73
- setState({ type: "is-dragging-over", edge });
74
- },
75
- onDrag({ self }) {
76
- let edge = extractClosestEdge(self.data);
77
- setState((current) => {
78
- if (current.type === "is-dragging-over" && current.edge === edge) {
79
- return current;
80
- }
81
- return { type: "is-dragging-over", edge };
82
- });
83
- },
84
- onDragLeave() {
85
- setState({ type: "idle" });
86
- },
87
- onDrop() {
88
- setState({ type: "idle" });
89
- },
90
- }),
91
- );
92
- }, [enabled, record, type, itemRef, handleRef]);
93
-
94
- return state;
95
- }
1
+ import { combine } from "@atlaskit/pragmatic-drag-and-drop/utils/combine";
2
+ import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/adapter/element-adapter";
3
+ import {
4
+ attachClosestEdge,
5
+ type Edge,
6
+ extractClosestEdge,
7
+ } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
8
+ import { type RefObject, useEffect, useState } from "react";
9
+
10
+ import { isSortableDragData, type SortableDragData } from "./useSortableContainer.js";
11
+ import type { SortableRecord } from "./types.js";
12
+
13
+ export type SortableItemState =
14
+ | { type: "idle" }
15
+ | { type: "is-dragging" }
16
+ | { type: "is-dragging-over"; edge: Edge | null };
17
+
18
+ export interface UseSortableItemOptions<T extends SortableRecord> {
19
+ /** Identifies the list. Must match the type given to the container/list */
20
+ type: string;
21
+ record: T;
22
+ /** Element that acts as the drop target, usually the list item */
23
+ itemRef: RefObject<HTMLElement | null>;
24
+ /** Element that starts the drag, usually a drag handle button */
25
+ handleRef: RefObject<HTMLElement | null>;
26
+ enabled?: boolean;
27
+ }
28
+
29
+ /**
30
+ * Makes a single list item draggable and turns it into a drop target that
31
+ * reports which edge is closest to the pointer, so a drop indicator can be
32
+ * rendered.
33
+ */
34
+ export function useSortableItem<T extends SortableRecord>({
35
+ type,
36
+ record,
37
+ itemRef,
38
+ handleRef,
39
+ enabled = true,
40
+ }: UseSortableItemOptions<T>): SortableItemState {
41
+ let [state, setState] = useState<SortableItemState>({ type: "idle" });
42
+
43
+ useEffect(() => {
44
+ if (!enabled) return;
45
+ let item = itemRef.current;
46
+ let handle = handleRef.current;
47
+ if (!item || !handle) return;
48
+
49
+ return combine(
50
+ draggable({
51
+ element: handle,
52
+ getInitialData: () => ({ type, record }) satisfies SortableDragData<T>,
53
+ onDragStart: () => setState({ type: "is-dragging" }),
54
+ onDrop: () => setState({ type: "idle" }),
55
+ }),
56
+ dropTargetForElements({
57
+ element: item,
58
+ canDrop({ source }) {
59
+ if (!isSortableDragData<T>(source.data, type)) return false;
60
+ return source.data.record.PrimKey !== record.PrimKey;
61
+ },
62
+ getData({ input }) {
63
+ let data: SortableDragData<T> = { type, record };
64
+ return attachClosestEdge(data, {
65
+ element: item,
66
+ input,
67
+ allowedEdges: ["top", "bottom"],
68
+ });
69
+ },
70
+ getIsSticky: () => true,
71
+ onDragEnter({ self }) {
72
+ let edge = extractClosestEdge(self.data);
73
+ setState({ type: "is-dragging-over", edge });
74
+ },
75
+ onDrag({ self }) {
76
+ let edge = extractClosestEdge(self.data);
77
+ setState((current) => {
78
+ if (current.type === "is-dragging-over" && current.edge === edge) {
79
+ return current;
80
+ }
81
+ return { type: "is-dragging-over", edge };
82
+ });
83
+ },
84
+ onDragLeave() {
85
+ setState({ type: "idle" });
86
+ },
87
+ onDrop() {
88
+ setState({ type: "idle" });
89
+ },
90
+ }),
91
+ );
92
+ }, [enabled, record, type, itemRef, handleRef]);
93
+
94
+ return state;
95
+ }
@@ -1,117 +1,117 @@
1
- import type { Edge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
2
- import type { DataObject } from "@olenbetong/appframe-data";
3
- import { useData } from "@olenbetong/appframe-react";
4
- import { useCallback, useMemo } from "react";
5
-
6
- import { reorderSortOrder } from "./reorderSortOrder.js";
7
- import { DEFAULT_SORT_ORDER_SPACING } from "./sortOrder.js";
8
- import type { SortableRecord } from "./types.js";
9
- import { useSortableContainer } from "./useSortableContainer.js";
10
-
11
- export interface SortableListOptions<T extends SortableRecord> {
12
- /** Data object holding the records to display */
13
- dataObject: DataObject<T>;
14
- /**
15
- * Data object used to persist the new sort order, when the list is bound to
16
- * a read-only view. Defaults to `dataObject`.
17
- */
18
- updateDataObject?: DataObject<any>;
19
- /**
20
- * Identifies the list. Only items sharing the same type can be dropped on
21
- * each other. Defaults to the data object id.
22
- */
23
- type?: string;
24
- /** Limits the list to a subset of the records, e.g. rows for the current master record */
25
- filter?: (record: T) => boolean;
26
- /** Disables dragging, e.g. while the list is read only */
27
- enabled?: boolean;
28
- /** Spacing used when appending to the end of the list */
29
- spacing?: number;
30
- /**
31
- * Called when the list runs out of free sort order values (two records share
32
- * the same sort order). Typically executes a stored procedure that renumbers
33
- * the list with even spacing.
34
- */
35
- onNormalize?: () => Promise<void>;
36
- onError?: (error: unknown) => void;
37
- }
38
-
39
- export interface SortableList<T extends SortableRecord> {
40
- /** Records passing `filter`, sorted by `SortOrder` */
41
- records: T[];
42
- /** Identifies the list. Pass to `useSortableItem` when rendering items manually */
43
- type: string;
44
- enabled: boolean;
45
- /**
46
- * Moves a record relative to another record. `edge` decides whether it is
47
- * placed before (`null`/`"top"`) or after (`"bottom"`) the target.
48
- */
49
- moveItem: (sourcePrimKey: string, targetPrimKey: string, edge: Edge | null) => Promise<void>;
50
- }
51
-
52
- /**
53
- * Keeps a data object sorted by its `SortOrder` column and handles drops from
54
- * the sortable items rendered inside it.
55
- *
56
- * Use {@link SortableItemList} for the common list layout, or this hook
57
- * directly together with {@link useSortableItem} and {@link DropIndicator}
58
- * when the items need custom markup.
59
- */
60
- export function useSortableList<T extends SortableRecord>({
61
- dataObject,
62
- updateDataObject,
63
- type,
64
- filter,
65
- enabled = true,
66
- spacing = DEFAULT_SORT_ORDER_SPACING,
67
- onNormalize,
68
- onError,
69
- }: SortableListOptions<T>): SortableList<T> {
70
- let data = useData(dataObject);
71
- let listType = type ?? dataObject.getDataSourceId();
72
- let dataObjectForUpdate = updateDataObject ?? dataObject;
73
- let records = useMemo(() => {
74
- let filtered = filter ? data.filter(filter) : data;
75
- return [...filtered].sort((a, b) => (a.SortOrder ?? spacing) - (b.SortOrder ?? spacing));
76
- }, [data, filter, spacing]);
77
-
78
- let moveItem = useCallback(
79
- async (sourcePrimKey: string, targetPrimKey: string, edge: Edge | null) => {
80
- try {
81
- await reorderSortOrder({
82
- records,
83
- sourcePrimKey,
84
- targetPrimKey,
85
- edge,
86
- spacing,
87
- onNormalize,
88
- refreshRecords: async () => {
89
- await dataObject.refreshDataSource();
90
- let refreshed = dataObject.getData();
91
- let filtered = filter ? refreshed.filter(filter) : refreshed;
92
- return [...filtered].sort((a, b) => (a.SortOrder ?? spacing) - (b.SortOrder ?? spacing));
93
- },
94
- listDataObject: dataObject,
95
- updateDataObject: dataObjectForUpdate,
96
- });
97
- } catch (error) {
98
- if (onError) {
99
- onError(error);
100
- } else {
101
- throw error;
102
- }
103
- }
104
- },
105
- [records, spacing, onNormalize, dataObject, dataObjectForUpdate, filter, onError],
106
- );
107
-
108
- useSortableContainer<T>({
109
- type: listType,
110
- enabled,
111
- onDrop: ({ source, target, edge }) => {
112
- void moveItem(source.PrimKey, target.PrimKey, edge);
113
- },
114
- });
115
-
116
- return { records, type: listType, enabled, moveItem };
117
- }
1
+ import type { Edge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
2
+ import type { DataObject } from "@olenbetong/appframe-data";
3
+ import { useData } from "@olenbetong/appframe-react";
4
+ import { useCallback, useMemo } from "react";
5
+
6
+ import { reorderSortOrder } from "./reorderSortOrder.js";
7
+ import { DEFAULT_SORT_ORDER_SPACING } from "./sortOrder.js";
8
+ import type { SortableRecord } from "./types.js";
9
+ import { useSortableContainer } from "./useSortableContainer.js";
10
+
11
+ export interface SortableListOptions<T extends SortableRecord> {
12
+ /** Data object holding the records to display */
13
+ dataObject: DataObject<T>;
14
+ /**
15
+ * Data object used to persist the new sort order, when the list is bound to
16
+ * a read-only view. Defaults to `dataObject`.
17
+ */
18
+ updateDataObject?: DataObject<any>;
19
+ /**
20
+ * Identifies the list. Only items sharing the same type can be dropped on
21
+ * each other. Defaults to the data object id.
22
+ */
23
+ type?: string;
24
+ /** Limits the list to a subset of the records, e.g. rows for the current master record */
25
+ filter?: (record: T) => boolean;
26
+ /** Disables dragging, e.g. while the list is read only */
27
+ enabled?: boolean;
28
+ /** Spacing used when appending to the end of the list */
29
+ spacing?: number;
30
+ /**
31
+ * Called when the list runs out of free sort order values (two records share
32
+ * the same sort order). Typically executes a stored procedure that renumbers
33
+ * the list with even spacing.
34
+ */
35
+ onNormalize?: () => Promise<void>;
36
+ onError?: (error: unknown) => void;
37
+ }
38
+
39
+ export interface SortableList<T extends SortableRecord> {
40
+ /** Records passing `filter`, sorted by `SortOrder` */
41
+ records: T[];
42
+ /** Identifies the list. Pass to `useSortableItem` when rendering items manually */
43
+ type: string;
44
+ enabled: boolean;
45
+ /**
46
+ * Moves a record relative to another record. `edge` decides whether it is
47
+ * placed before (`null`/`"top"`) or after (`"bottom"`) the target.
48
+ */
49
+ moveItem: (sourcePrimKey: string, targetPrimKey: string, edge: Edge | null) => Promise<void>;
50
+ }
51
+
52
+ /**
53
+ * Keeps a data object sorted by its `SortOrder` column and handles drops from
54
+ * the sortable items rendered inside it.
55
+ *
56
+ * Use {@link SortableItemList} for the common list layout, or this hook
57
+ * directly together with {@link useSortableItem} and {@link DropIndicator}
58
+ * when the items need custom markup.
59
+ */
60
+ export function useSortableList<T extends SortableRecord>({
61
+ dataObject,
62
+ updateDataObject,
63
+ type,
64
+ filter,
65
+ enabled = true,
66
+ spacing = DEFAULT_SORT_ORDER_SPACING,
67
+ onNormalize,
68
+ onError,
69
+ }: SortableListOptions<T>): SortableList<T> {
70
+ let data = useData(dataObject);
71
+ let listType = type ?? dataObject.getDataSourceId();
72
+ let dataObjectForUpdate = updateDataObject ?? dataObject;
73
+ let records = useMemo(() => {
74
+ let filtered = filter ? data.filter(filter) : data;
75
+ return [...filtered].sort((a, b) => (a.SortOrder ?? spacing) - (b.SortOrder ?? spacing));
76
+ }, [data, filter, spacing]);
77
+
78
+ let moveItem = useCallback(
79
+ async (sourcePrimKey: string, targetPrimKey: string, edge: Edge | null) => {
80
+ try {
81
+ await reorderSortOrder({
82
+ records,
83
+ sourcePrimKey,
84
+ targetPrimKey,
85
+ edge,
86
+ spacing,
87
+ onNormalize,
88
+ refreshRecords: async () => {
89
+ await dataObject.refreshDataSource();
90
+ let refreshed = dataObject.getData();
91
+ let filtered = filter ? refreshed.filter(filter) : refreshed;
92
+ return [...filtered].sort((a, b) => (a.SortOrder ?? spacing) - (b.SortOrder ?? spacing));
93
+ },
94
+ listDataObject: dataObject,
95
+ updateDataObject: dataObjectForUpdate,
96
+ });
97
+ } catch (error) {
98
+ if (onError) {
99
+ onError(error);
100
+ } else {
101
+ throw error;
102
+ }
103
+ }
104
+ },
105
+ [records, spacing, onNormalize, dataObject, dataObjectForUpdate, filter, onError],
106
+ );
107
+
108
+ useSortableContainer<T>({
109
+ type: listType,
110
+ enabled,
111
+ onDrop: ({ source, target, edge }) => {
112
+ void moveItem(source.PrimKey, target.PrimKey, edge);
113
+ },
114
+ });
115
+
116
+ return { records, type: listType, enabled, moveItem };
117
+ }
@@ -1,24 +1,24 @@
1
- /**
2
- * happy-dom exposes a `Document` constructor on the window that is *not* in the
3
- * prototype chain of the actual `document` (which is an `HTMLDocument` built on
4
- * happy-dom's internal `Document` class). `document instanceof Document` is
5
- * therefore `false`, which is enough to hang libraries that walk up the tree
6
- * until they reach the document — the `@oddbird/popover-polyfill` bundled into
7
- * `@digdir/designsystemet-web` loops forever in `nearestInclusiveOpenPopover`,
8
- * taking the whole worker with it.
9
- *
10
- * Point the global `Document` at the class the document really inherits from.
11
- * Must run before anything that captures `Document`.
12
- */
13
- export function fixHappyDomDocumentClass(): void {
14
- if (typeof document === "undefined" || document instanceof Document) return;
15
-
16
- for (let proto = Object.getPrototypeOf(document); proto; proto = Object.getPrototypeOf(proto)) {
17
- let ctor = proto.constructor;
18
- if (ctor?.name === "Document") {
19
- globalThis.Document = ctor;
20
- (globalThis as typeof globalThis & { window: typeof globalThis }).window.Document = ctor;
21
- return;
22
- }
23
- }
24
- }
1
+ /**
2
+ * happy-dom exposes a `Document` constructor on the window that is *not* in the
3
+ * prototype chain of the actual `document` (which is an `HTMLDocument` built on
4
+ * happy-dom's internal `Document` class). `document instanceof Document` is
5
+ * therefore `false`, which is enough to hang libraries that walk up the tree
6
+ * until they reach the document — the `@oddbird/popover-polyfill` bundled into
7
+ * `@digdir/designsystemet-web` loops forever in `nearestInclusiveOpenPopover`,
8
+ * taking the whole worker with it.
9
+ *
10
+ * Point the global `Document` at the class the document really inherits from.
11
+ * Must run before anything that captures `Document`.
12
+ */
13
+ export function fixHappyDomDocumentClass(): void {
14
+ if (typeof document === "undefined" || document instanceof Document) return;
15
+
16
+ for (let proto = Object.getPrototypeOf(document); proto; proto = Object.getPrototypeOf(proto)) {
17
+ let ctor = proto.constructor;
18
+ if (ctor?.name === "Document") {
19
+ globalThis.Document = ctor;
20
+ (globalThis as typeof globalThis & { window: typeof globalThis }).window.Document = ctor;
21
+ return;
22
+ }
23
+ }
24
+ }
package/src/test/setup.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { fixHappyDomDocumentClass } from "./happy-dom-document-class.js";
2
-
3
- fixHappyDomDocumentClass();
4
-
5
- import "@testing-library/jest-dom/vitest";
6
-
7
- import { cleanup } from "@testing-library/react";
8
- import { afterEach } from "vitest";
9
-
10
- afterEach(() => {
11
- cleanup();
12
- });
1
+ import { fixHappyDomDocumentClass } from "./happy-dom-document-class.js";
2
+
3
+ fixHappyDomDocumentClass();
4
+
5
+ import "@testing-library/jest-dom/vitest";
6
+
7
+ import { cleanup } from "@testing-library/react";
8
+ import { afterEach } from "vitest";
9
+
10
+ afterEach(() => {
11
+ cleanup();
12
+ });
@@ -1,53 +1,53 @@
1
- import { useEffect, useState } from "react";
2
-
3
- export function getDsColorScheme(element?: Element | null): "light" | "dark" {
4
- let scoped = element?.closest<HTMLElement>("[data-color-scheme]");
5
- let scheme =
6
- scoped?.dataset.colorScheme ??
7
- document.body?.dataset.colorScheme ??
8
- document.documentElement?.dataset.colorScheme ??
9
- "light";
10
-
11
- if (scheme === "auto") {
12
- return globalThis.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
13
- }
14
-
15
- return scheme === "dark" ? "dark" : "light";
16
- }
17
-
18
- /**
19
- * Tracks the color scheme Designsystemet is rendering with. The article template
20
- * sets `data-color-scheme` on `<body>`, but it may also be scoped to a subtree,
21
- * so resolve it from `element` upwards when one is given. The OS preference is
22
- * watched too, since it decides the result when the scheme is `auto`.
23
- */
24
- export function useDsColorScheme(element?: Element | null) {
25
- let [scheme, setScheme] = useState<"light" | "dark">(() => getDsColorScheme(element));
26
-
27
- useEffect(() => {
28
- function update() {
29
- setScheme(getDsColorScheme(element));
30
- }
31
-
32
- update();
33
-
34
- // `data-color-scheme` may sit on any ancestor, so watch the whole document
35
- // for that one attribute rather than just `<html>` and `<body>`.
36
- let observer = new MutationObserver(update);
37
- observer.observe(document.documentElement, {
38
- attributes: true,
39
- attributeFilter: ["data-color-scheme"],
40
- subtree: true,
41
- });
42
-
43
- let media = globalThis.matchMedia?.("(prefers-color-scheme: dark)");
44
- media?.addEventListener("change", update);
45
-
46
- return () => {
47
- observer.disconnect();
48
- media?.removeEventListener("change", update);
49
- };
50
- }, [element]);
51
-
52
- return scheme;
53
- }
1
+ import { useEffect, useState } from "react";
2
+
3
+ export function getDsColorScheme(element?: Element | null): "light" | "dark" {
4
+ let scoped = element?.closest<HTMLElement>("[data-color-scheme]");
5
+ let scheme =
6
+ scoped?.dataset.colorScheme ??
7
+ document.body?.dataset.colorScheme ??
8
+ document.documentElement?.dataset.colorScheme ??
9
+ "light";
10
+
11
+ if (scheme === "auto") {
12
+ return globalThis.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
13
+ }
14
+
15
+ return scheme === "dark" ? "dark" : "light";
16
+ }
17
+
18
+ /**
19
+ * Tracks the color scheme Designsystemet is rendering with. The article template
20
+ * sets `data-color-scheme` on `<body>`, but it may also be scoped to a subtree,
21
+ * so resolve it from `element` upwards when one is given. The OS preference is
22
+ * watched too, since it decides the result when the scheme is `auto`.
23
+ */
24
+ export function useDsColorScheme(element?: Element | null) {
25
+ let [scheme, setScheme] = useState<"light" | "dark">(() => getDsColorScheme(element));
26
+
27
+ useEffect(() => {
28
+ function update() {
29
+ setScheme(getDsColorScheme(element));
30
+ }
31
+
32
+ update();
33
+
34
+ // `data-color-scheme` may sit on any ancestor, so watch the whole document
35
+ // for that one attribute rather than just `<html>` and `<body>`.
36
+ let observer = new MutationObserver(update);
37
+ observer.observe(document.documentElement, {
38
+ attributes: true,
39
+ attributeFilter: ["data-color-scheme"],
40
+ subtree: true,
41
+ });
42
+
43
+ let media = globalThis.matchMedia?.("(prefers-color-scheme: dark)");
44
+ media?.addEventListener("change", update);
45
+
46
+ return () => {
47
+ observer.disconnect();
48
+ media?.removeEventListener("change", update);
49
+ };
50
+ }, [element]);
51
+
52
+ return scheme;
53
+ }
@@ -1,9 +1,9 @@
1
- {
2
- "extends": "../tsconfig.build.base.json",
3
- "compilerOptions": {
4
- "rootDir": "./src",
5
- "outDir": "./es"
6
- },
7
- "include": ["src"],
8
- "exclude": ["es", "node_modules"]
9
- }
1
+ {
2
+ "extends": "../tsconfig.build.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./es"
6
+ },
7
+ "include": ["src"],
8
+ "exclude": ["es", "node_modules"]
9
+ }
package/tsconfig.json CHANGED
@@ -1,8 +1,8 @@
1
- {
2
- "extends": "../tsconfig.build.base.json",
3
- "compilerOptions": {
4
- "rootDir": "./src",
5
- "outDir": "./es"
6
- },
7
- "include": ["src"]
8
- }
1
+ {
2
+ "extends": "../tsconfig.build.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./es"
6
+ },
7
+ "include": ["src"]
8
+ }
package/vitest.config.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { defineConfig } from "vitest/config";
2
-
3
- export default defineConfig({
4
- test: {
5
- include: ["src/**/*.test.tsx", "src/**/*.test.ts"],
6
- environment: "happy-dom",
7
- setupFiles: ["src/test/setup.ts"],
8
- env: { NODE_ENV: "test" },
9
- },
10
- });
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ["src/**/*.test.tsx", "src/**/*.test.ts"],
6
+ environment: "happy-dom",
7
+ setupFiles: ["src/test/setup.ts"],
8
+ env: { NODE_ENV: "test" },
9
+ },
10
+ });