@olenbetong/appframe-ds 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/package.json +3 -3
  3. package/scripts/copyAssets.mjs +27 -27
  4. package/src/autocomplete/Autocomplete.css +253 -253
  5. package/src/autocomplete/Autocomplete.tsx +320 -320
  6. package/src/autocomplete/AutocompleteFrame.tsx +168 -168
  7. package/src/autocomplete/Combobox.tsx +286 -286
  8. package/src/autocomplete/index.ts +4 -4
  9. package/src/autocomplete/types.ts +204 -204
  10. package/src/autocomplete/utils.ts +183 -183
  11. package/src/binding/BoundNumberInput.tsx +42 -42
  12. package/src/binding/BoundNumericTextField.tsx +102 -102
  13. package/src/binding/BoundTextField.tsx +49 -49
  14. package/src/binding/CancelButton.tsx +30 -30
  15. package/src/binding/CancelIconButton.tsx +30 -30
  16. package/src/binding/DataEditToolbar.css +23 -23
  17. package/src/binding/DataEditToolbar.tsx +84 -84
  18. package/src/binding/DeleteButton.tsx +35 -35
  19. package/src/binding/DeleteIconButton.tsx +35 -35
  20. package/src/binding/RefreshButton.tsx +31 -31
  21. package/src/binding/RefreshIconButton.tsx +33 -33
  22. package/src/binding/RefreshRowIconButton.tsx +33 -33
  23. package/src/binding/SaveButton.tsx +37 -37
  24. package/src/binding/SaveIconButton.tsx +38 -38
  25. package/src/binding/index.ts +20 -20
  26. package/src/container/Container.tsx +50 -50
  27. package/src/container/index.ts +1 -1
  28. package/src/cutoff/CutOffPicker.css +12 -12
  29. package/src/cutoff/CutOffPicker.tsx +165 -165
  30. package/src/cutoff/context.tsx +194 -194
  31. package/src/cutoff/dsCutOffDates.ts +21 -21
  32. package/src/cutoff/index.ts +4 -4
  33. package/src/cutoff/utils.test.ts +59 -59
  34. package/src/cutoff/utils.ts +276 -276
  35. package/src/filter/ChipListInput.tsx +79 -79
  36. package/src/filter/FieldFilterPanel.css +150 -150
  37. package/src/filter/FieldFilterPanel.tsx +309 -309
  38. package/src/filter/FilterBuilder.css +152 -152
  39. package/src/filter/FilterBuilder.test.tsx +153 -153
  40. package/src/filter/FilterBuilder.tsx +435 -435
  41. package/src/filter/FilterEditor.css +231 -231
  42. package/src/filter/FilterEditor.test.tsx +285 -285
  43. package/src/filter/FilterEditor.tsx +75 -75
  44. package/src/filter/FilterGroupEditor.tsx +160 -160
  45. package/src/filter/FilterNameDialog.css +22 -22
  46. package/src/filter/FilterNameDialog.tsx +81 -81
  47. package/src/filter/FilterRow.tsx +117 -117
  48. package/src/filter/FilterShareDialog.css +28 -28
  49. package/src/filter/FilterShareDialog.tsx +201 -201
  50. package/src/filter/FilterStringField.tsx +78 -78
  51. package/src/filter/FilterValueEditor.tsx +239 -239
  52. package/src/filter/SavedFilterTree.css +107 -107
  53. package/src/filter/SavedFilterTree.test.tsx +94 -94
  54. package/src/filter/SavedFilterTree.tsx +218 -218
  55. package/src/filter/fieldFilter.test.ts +158 -158
  56. package/src/filter/fieldFilter.ts +139 -139
  57. package/src/filter/index.ts +27 -27
  58. package/src/filter/types.ts +38 -38
  59. package/src/filter/useDistinctValues.test.ts +102 -102
  60. package/src/filter/useDistinctValues.ts +230 -230
  61. package/src/global.d.ts +11 -11
  62. package/src/grid/formatters.ts +23 -0
  63. package/src/grid/index.css +8 -2
  64. package/src/grid/index.tsx +73 -3
  65. package/src/grid/useAfColumns.tsx +28 -4
  66. package/src/grid/useAfGridApi.ts +3 -0
  67. package/src/grid/utcDate.test.ts +25 -0
  68. package/src/grid/utcDate.ts +28 -0
  69. package/src/grid/utcDateEditing.tsx +11 -0
  70. package/src/i18n.ts +13 -13
  71. package/src/index.ts +14 -14
  72. package/src/input/InputAdornments.css +80 -80
  73. package/src/input/InputAdornments.tsx +102 -102
  74. package/src/input/NumberInput.css +9 -9
  75. package/src/input/NumberInput.test.tsx +212 -212
  76. package/src/input/NumberInput.tsx +268 -268
  77. package/src/input/index.ts +3 -3
  78. package/src/input/numberFormat.test.ts +79 -79
  79. package/src/input/numberFormat.ts +94 -94
  80. package/src/layout/AppMenu.tsx +74 -74
  81. package/src/layout/AppMenuDrawer.css +71 -71
  82. package/src/layout/AppMenuDrawer.tsx +122 -122
  83. package/src/layout/AppMenuItem.css +6 -2
  84. package/src/layout/DialogMotion.css +79 -79
  85. package/src/layout/ErrorBoundary.tsx +162 -162
  86. package/src/layout/PageContainer.css +48 -48
  87. package/src/layout/PageContainer.tsx +41 -41
  88. package/src/layout/ResponsiveDialog.css +86 -86
  89. package/src/layout/ResponsiveDialog.tsx +55 -55
  90. package/src/layout/index.tsx +70 -70
  91. package/src/link/Link.tsx +23 -23
  92. package/src/link/index.ts +1 -1
  93. package/src/mdi/Mdi.css +8 -8
  94. package/src/mdi/Mdi.test.tsx +91 -91
  95. package/src/mdi/Mdi.tsx +141 -141
  96. package/src/mdi/MdiConfirmCloseDialog.tsx +47 -47
  97. package/src/mdi/MdiContext.tsx +59 -59
  98. package/src/mdi/MdiDocumentContext.tsx +60 -60
  99. package/src/mdi/MdiPanel.tsx +61 -61
  100. package/src/mdi/MdiRestore.test.tsx +86 -86
  101. package/src/mdi/MdiTab.tsx +30 -30
  102. package/src/mdi/controller.test.ts +382 -382
  103. package/src/mdi/controller.ts +656 -656
  104. package/src/mdi/dockviewHost.ts +132 -132
  105. package/src/mdi/fakeHost.ts +200 -200
  106. package/src/mdi/host.ts +44 -44
  107. package/src/mdi/ids.test.ts +20 -20
  108. package/src/mdi/ids.ts +18 -18
  109. package/src/mdi/index.ts +31 -31
  110. package/src/mdi/layoutRestore.test.ts +156 -156
  111. package/src/mdi/layoutRestore.ts +311 -311
  112. package/src/mdi/layoutSpec.test.ts +143 -143
  113. package/src/mdi/layoutSpec.ts +214 -214
  114. package/src/mdi/layoutStore.test.ts +93 -93
  115. package/src/mdi/layoutStore.ts +99 -99
  116. package/src/mdi/memorySettingsStore.ts +21 -21
  117. package/src/mdi/registry.ts +34 -34
  118. package/src/mdi/testTypes.ts +37 -37
  119. package/src/mdi/theme.ts +22 -22
  120. package/src/mdi/types.ts +214 -214
  121. package/src/page/Page.css +57 -57
  122. package/src/page/PageBlock.css +64 -64
  123. package/src/page/PageBlock.tsx +93 -93
  124. package/src/page/PageHeader.css +88 -88
  125. package/src/page/PageHeader.tsx +115 -115
  126. package/src/page/PageTitle.css +5 -5
  127. package/src/page/PageTitle.tsx +21 -21
  128. package/src/page/index.ts +4 -4
  129. package/src/paper/Paper.tsx +52 -52
  130. package/src/progress/LinearProgress.css +121 -121
  131. package/src/progress/LinearProgress.tsx +106 -106
  132. package/src/progress/index.ts +1 -1
  133. package/src/report-downloader/ReportDownloader.css +67 -67
  134. package/src/report-downloader/components/StatusItem.tsx +87 -87
  135. package/src/report-downloader/components/StatusList.tsx +28 -28
  136. package/src/report-downloader/index.ts +41 -41
  137. package/src/report-downloader/status.tsx +92 -92
  138. package/src/sortable/DropIndicator.css +51 -51
  139. package/src/sortable/DropIndicator.tsx +27 -27
  140. package/src/sortable/SortableItemList.css +21 -21
  141. package/src/sortable/SortableItemList.tsx +264 -264
  142. package/src/sortable/index.ts +8 -8
  143. package/src/sortable/reorderSortOrder.ts +126 -126
  144. package/src/sortable/sortOrder.ts +101 -101
  145. package/src/sortable/types.ts +9 -9
  146. package/src/sortable/useSortableContainer.ts +53 -53
  147. package/src/sortable/useSortableItem.ts +95 -95
  148. package/src/sortable/useSortableList.ts +117 -117
  149. package/src/test/happy-dom-document-class.ts +24 -24
  150. package/src/test/setup.ts +27 -27
  151. package/src/theme/useDsColorScheme.ts +53 -53
  152. package/tsconfig.build.json +9 -9
  153. package/tsconfig.json +8 -8
  154. 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,27 +1,27 @@
1
- import { fixHappyDomDocumentClass } from "./happy-dom-document-class.js";
2
-
3
- fixHappyDomDocumentClass();
4
-
5
- import "@testing-library/jest-dom/vitest";
6
-
7
- import type { TestingLibraryMatchers } from "@testing-library/jest-dom/matchers";
8
-
9
- import { cleanup } from "@testing-library/react";
10
- import { afterEach } from "vitest";
11
-
12
- afterEach(() => {
13
- cleanup();
14
- });
15
-
16
- /*
17
- * jest-dom 7.0.1 types its matchers onto the one-parameter `Assertion<T>` of Vitest 4, which
18
- * Vitest 5 replaced with `Assertion<R, T>`; custom matchers now go on `Matchers<R, T>`. Until
19
- * jest-dom ships Vitest 5 types, register them there. Remove this when its `vitest` entry
20
- * types `Matchers` itself.
21
- */
22
- declare module "vitest" {
23
- interface Matchers<R extends void | Promise<void> = void | Promise<void>, T = unknown> extends TestingLibraryMatchers<
24
- T,
25
- R
26
- > {}
27
- }
1
+ import { fixHappyDomDocumentClass } from "./happy-dom-document-class.js";
2
+
3
+ fixHappyDomDocumentClass();
4
+
5
+ import "@testing-library/jest-dom/vitest";
6
+
7
+ import type { TestingLibraryMatchers } from "@testing-library/jest-dom/matchers";
8
+
9
+ import { cleanup } from "@testing-library/react";
10
+ import { afterEach } from "vitest";
11
+
12
+ afterEach(() => {
13
+ cleanup();
14
+ });
15
+
16
+ /*
17
+ * jest-dom 7.0.1 types its matchers onto the one-parameter `Assertion<T>` of Vitest 4, which
18
+ * Vitest 5 replaced with `Assertion<R, T>`; custom matchers now go on `Matchers<R, T>`. Until
19
+ * jest-dom ships Vitest 5 types, register them there. Remove this when its `vitest` entry
20
+ * types `Matchers` itself.
21
+ */
22
+ declare module "vitest" {
23
+ interface Matchers<R extends void | Promise<void> = void | Promise<void>, T = unknown> extends TestingLibraryMatchers<
24
+ T,
25
+ R
26
+ > {}
27
+ }
@@ -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
+ }