@jsenv/navi 0.10.1 → 0.11.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 (207) hide show
  1. package/dist/jsenv_navi.js +13858 -23295
  2. package/dist/jsenv_navi.js.map +1281 -0
  3. package/package.json +5 -7
  4. package/index.js +0 -122
  5. package/src/action_private_properties.js +0 -11
  6. package/src/action_proxy_test.html +0 -353
  7. package/src/action_run_states.js +0 -5
  8. package/src/actions.js +0 -1401
  9. package/src/browser_integration/browser_integration.js +0 -216
  10. package/src/browser_integration/document_back_and_forward.js +0 -17
  11. package/src/browser_integration/document_loading_signal.js +0 -100
  12. package/src/browser_integration/document_state_signal.js +0 -9
  13. package/src/browser_integration/document_url_signal.js +0 -9
  14. package/src/browser_integration/use_is_visited.js +0 -19
  15. package/src/browser_integration/via_history.js +0 -232
  16. package/src/browser_integration/via_navigation.js +0 -168
  17. package/src/components/action_execution/form_context.js +0 -5
  18. package/src/components/action_execution/render_actionable_component.jsx +0 -29
  19. package/src/components/action_execution/use_action.js +0 -99
  20. package/src/components/action_execution/use_execute_action.js +0 -177
  21. package/src/components/action_execution/use_run_on_mount.js +0 -9
  22. package/src/components/action_renderer.jsx +0 -125
  23. package/src/components/callout/callout.js +0 -990
  24. package/src/components/callout/callout_demo.html +0 -201
  25. package/src/components/callout/test_dynamic_positioning.html +0 -161
  26. package/src/components/callout/test_html_document_iframe.html +0 -182
  27. package/src/components/demos/0_button_demo.html +0 -707
  28. package/src/components/demos/10_column_reordering_debug.html +0 -277
  29. package/src/components/demos/11_table_selection_debug.html +0 -432
  30. package/src/components/demos/1_checkbox_demo.html +0 -754
  31. package/src/components/demos/2_input_textual_demo.html +0 -286
  32. package/src/components/demos/3_radio_demo.html +0 -874
  33. package/src/components/demos/4_select_demo.html +0 -100
  34. package/src/components/demos/5_list_scrollable_demo.html +0 -153
  35. package/src/components/demos/6_tablist_demo.html +0 -77
  36. package/src/components/demos/7_table_selection_demo.html +0 -176
  37. package/src/components/demos/8_table_fixed_headers_demo.html +0 -584
  38. package/src/components/demos/9_table_column_drag_demo.html +0 -325
  39. package/src/components/demos/action/0_button_demo.html +0 -204
  40. package/src/components/demos/action/10_shortcuts_demo.html +0 -189
  41. package/src/components/demos/action/11_nested_shortcuts_demo.xhtml +0 -401
  42. package/src/components/demos/action/1_input_text_demo.html +0 -876
  43. package/src/components/demos/action/2_form_multiple.html +0 -303
  44. package/src/components/demos/action/3_details_demo.html +0 -203
  45. package/src/components/demos/action/4_input_checkbox_demo.html +0 -731
  46. package/src/components/demos/action/5_input_checkbox_state_demo.html +0 -270
  47. package/src/components/demos/action/6_checkbox_list_demo.html +0 -341
  48. package/src/components/demos/action/7_radio_list_demo.html +0 -357
  49. package/src/components/demos/action/8_editable_demo.html +0 -431
  50. package/src/components/demos/action/9_link_demo.html +0 -194
  51. package/src/components/demos/demo.md +0 -0
  52. package/src/components/demos/route/basic/basic.html +0 -14
  53. package/src/components/demos/route/basic/basic_route_demo.jsx +0 -224
  54. package/src/components/demos/route/multi/multi.html +0 -14
  55. package/src/components/demos/route/multi/multi_route_demo.jsx +0 -277
  56. package/src/components/demos/ui_transition/0_action_renderer_ui_transition_demo.html +0 -695
  57. package/src/components/demos/ui_transition/1_nested_ui_transition_demo.html +0 -429
  58. package/src/components/demos/ui_transition/2_height_transition_test.html +0 -295
  59. package/src/components/details/details.jsx +0 -245
  60. package/src/components/details/summary_marker.jsx +0 -141
  61. package/src/components/edition/editable.jsx +0 -186
  62. package/src/components/error_boundary_context.js +0 -9
  63. package/src/components/field/README.md +0 -247
  64. package/src/components/field/button.jsx +0 -429
  65. package/src/components/field/checkbox_list.jsx +0 -185
  66. package/src/components/field/collect_form_element_values.js +0 -82
  67. package/src/components/field/custom_field.js +0 -106
  68. package/src/components/field/form.jsx +0 -209
  69. package/src/components/field/input.jsx +0 -16
  70. package/src/components/field/input_checkbox.jsx +0 -434
  71. package/src/components/field/input_radio.jsx +0 -432
  72. package/src/components/field/input_textual.jsx +0 -389
  73. package/src/components/field/label.jsx +0 -46
  74. package/src/components/field/radio_list.jsx +0 -183
  75. package/src/components/field/select.jsx +0 -256
  76. package/src/components/field/use_action_events.js +0 -132
  77. package/src/components/field/use_form_events.js +0 -59
  78. package/src/components/field/use_ui_state_controller.js +0 -506
  79. package/src/components/item_tracker/README.md +0 -461
  80. package/src/components/item_tracker/use_isolated_item_tracker.jsx +0 -209
  81. package/src/components/item_tracker/use_isolated_item_tracker_demo.html +0 -148
  82. package/src/components/item_tracker/use_isolated_item_tracker_demo.jsx +0 -460
  83. package/src/components/item_tracker/use_item_tracker.jsx +0 -143
  84. package/src/components/item_tracker/use_item_tracker_demo.html +0 -207
  85. package/src/components/item_tracker/use_item_tracker_demo.jsx +0 -216
  86. package/src/components/keyboard_shortcuts/active_keyboard_shortcuts.jsx +0 -87
  87. package/src/components/keyboard_shortcuts/aria_key_shortcuts.js +0 -61
  88. package/src/components/keyboard_shortcuts/keyboard_key_meta.js +0 -17
  89. package/src/components/keyboard_shortcuts/keyboard_shortcuts.js +0 -371
  90. package/src/components/keyboard_shortcuts/os.js +0 -9
  91. package/src/components/layout/demos/demo_flex.html +0 -638
  92. package/src/components/layout/demos/demo_layout_style_buttons.html +0 -351
  93. package/src/components/layout/demos/demo_layout_style_input.html +0 -226
  94. package/src/components/layout/demos/demo_layout_style_text.html +0 -514
  95. package/src/components/layout/flex.jsx +0 -109
  96. package/src/components/layout/layout_context.jsx +0 -3
  97. package/src/components/layout/spacing.jsx +0 -20
  98. package/src/components/layout/use_layout_style.js +0 -249
  99. package/src/components/link/link.jsx +0 -267
  100. package/src/components/link/link_with_icon.jsx +0 -52
  101. package/src/components/loader/loader_background.jsx +0 -372
  102. package/src/components/loader/loading_spinner.jsx +0 -68
  103. package/src/components/loader/network_speed.js +0 -83
  104. package/src/components/loader/rectangle_loading.jsx +0 -244
  105. package/src/components/props_composition/demos/demo_with_props_style.html +0 -81
  106. package/src/components/props_composition/with_props_class_name.js +0 -37
  107. package/src/components/props_composition/with_props_style.js +0 -26
  108. package/src/components/route.jsx +0 -19
  109. package/src/components/selection/selection.jsx +0 -1583
  110. package/src/components/svg/font_sized_svg.jsx +0 -59
  111. package/src/components/svg/icon_and_text.jsx +0 -21
  112. package/src/components/svg/svg_mask_overlay.jsx +0 -105
  113. package/src/components/table/drag/table_drag.jsx +0 -506
  114. package/src/components/table/resize/table_resize.jsx +0 -650
  115. package/src/components/table/resize/table_size.js +0 -43
  116. package/src/components/table/selection/table_selection.js +0 -106
  117. package/src/components/table/selection/table_selection.jsx +0 -203
  118. package/src/components/table/sticky/sticky_group.js +0 -354
  119. package/src/components/table/sticky/table_sticky.js +0 -25
  120. package/src/components/table/sticky/table_sticky.jsx +0 -501
  121. package/src/components/table/table.jsx +0 -721
  122. package/src/components/table/table_css.js +0 -211
  123. package/src/components/table/table_ui.jsx +0 -49
  124. package/src/components/table/use_cells_and_columns.js +0 -90
  125. package/src/components/table/use_object_array_to_cells.js +0 -46
  126. package/src/components/table/z_indexes.js +0 -23
  127. package/src/components/tablist/tablist.jsx +0 -99
  128. package/src/components/text/demos/demo_text_and_icon.html +0 -421
  129. package/src/components/text/overflow.jsx +0 -15
  130. package/src/components/text/text.jsx +0 -83
  131. package/src/components/text/text_and_count.jsx +0 -28
  132. package/src/components/ui_transition.jsx +0 -128
  133. package/src/components/use_auto_focus.js +0 -94
  134. package/src/components/use_batch_during_render.js +0 -33
  135. package/src/components/use_debounce_true.js +0 -31
  136. package/src/components/use_dependencies_diff.js +0 -35
  137. package/src/components/use_focus_group.js +0 -20
  138. package/src/components/use_initial_value.js +0 -78
  139. package/src/components/use_is_visited.js +0 -19
  140. package/src/components/use_ref_array.js +0 -38
  141. package/src/components/use_signal_sync.js +0 -50
  142. package/src/components/use_stable_callback.js +0 -68
  143. package/src/components/use_state_array.js +0 -47
  144. package/src/docs/actions.md +0 -250
  145. package/src/docs/demos/resource/action_status.jsx +0 -42
  146. package/src/docs/demos/resource/demo.md +0 -1
  147. package/src/docs/demos/resource/resource_demo_0.html +0 -84
  148. package/src/docs/demos/resource/resource_demo_10_post_gc.html +0 -364
  149. package/src/docs/demos/resource/resource_demo_11_describe_many.html +0 -362
  150. package/src/docs/demos/resource/resource_demo_2.html +0 -173
  151. package/src/docs/demos/resource/resource_demo_3_filtered_users.html +0 -415
  152. package/src/docs/demos/resource/resource_demo_4_details.html +0 -284
  153. package/src/docs/demos/resource/resource_demo_5_renderer_lazy.html +0 -115
  154. package/src/docs/demos/resource/resource_demo_6_gc.html +0 -217
  155. package/src/docs/demos/resource/resource_demo_7_child_gc.html +0 -240
  156. package/src/docs/demos/resource/resource_demo_8_proxy_gc.html +0 -319
  157. package/src/docs/demos/resource/resource_demo_9_describe_one.html +0 -472
  158. package/src/docs/demos/resource/tata.jsx +0 -3
  159. package/src/docs/demos/resource/toto.jsx +0 -3
  160. package/src/docs/demos/user_nav/user_nav.html +0 -12
  161. package/src/docs/demos/user_nav/user_nav.jsx +0 -330
  162. package/src/docs/resource_dependencies.md +0 -103
  163. package/src/docs/resource_with_params.md +0 -80
  164. package/src/navi_css_vars.js +0 -14
  165. package/src/notes.md +0 -34
  166. package/src/route/route.js +0 -596
  167. package/src/route/route.xtest.html +0 -228
  168. package/src/store/array_signal_store.js +0 -537
  169. package/src/store/local_storage_signal.js +0 -17
  170. package/src/store/resource_graph.js +0 -1304
  171. package/src/store/tests/resource_graph_autoreload_demo.html +0 -12
  172. package/src/store/tests/resource_graph_autoreload_demo.jsx +0 -964
  173. package/src/store/tests/resource_graph_dependencies.test_manual.js +0 -95
  174. package/src/store/value_in_local_storage.js +0 -187
  175. package/src/symbol_object_signal.js +0 -1
  176. package/src/use_action_data.js +0 -10
  177. package/src/use_action_status.js +0 -47
  178. package/src/utils/add_many_event_listeners.js +0 -15
  179. package/src/utils/array_add_remove.js +0 -61
  180. package/src/utils/array_signal.js +0 -15
  181. package/src/utils/compare_two_js_values.js +0 -172
  182. package/src/utils/execute_with_cleanup.js +0 -21
  183. package/src/utils/get_caller_info.js +0 -85
  184. package/src/utils/is_signal.js +0 -20
  185. package/src/utils/js_value_weak_map.js +0 -162
  186. package/src/utils/js_value_weak_map_demo.html +0 -690
  187. package/src/utils/merge_two_js_values.js +0 -53
  188. package/src/utils/stringify_for_display.js +0 -131
  189. package/src/utils/weak_effect.js +0 -48
  190. package/src/validation/constraints/confirm_constraint.js +0 -14
  191. package/src/validation/constraints/create_unique_value_constraint.js +0 -27
  192. package/src/validation/constraints/native_constraints.js +0 -338
  193. package/src/validation/constraints/readonly_constraint.js +0 -41
  194. package/src/validation/constraints/same_as_constraint.js +0 -42
  195. package/src/validation/constraints/single_space_constraint.js +0 -13
  196. package/src/validation/custom_constraint_validation.js +0 -793
  197. package/src/validation/custom_message.js +0 -18
  198. package/src/validation/demos/browser_style.png +0 -0
  199. package/src/validation/demos/demo_same_as_constraint.html +0 -259
  200. package/src/validation/demos/form_validation_demo.html +0 -142
  201. package/src/validation/demos/form_validation_demo_preact.html +0 -87
  202. package/src/validation/demos/form_validation_native_popover_demo.html +0 -168
  203. package/src/validation/demos/form_validation_vs_native_demo.html +0 -172
  204. package/src/validation/hooks/use_constraints.js +0 -23
  205. package/src/validation/hooks/use_custom_validation_ref.js +0 -73
  206. package/src/validation/hooks/use_validation_message.js +0 -19
  207. package/src/validation/input_change_effect.js +0 -106
@@ -1,59 +0,0 @@
1
- /**
2
- * FontSizedSvg component
3
- *
4
- * This component wraps an SVG element to make it inherit the current font size.
5
- * It creates a container that's exactly 1em × 1em in size, allowing the SVG to scale
6
- * proportionally with the surrounding text.
7
- *
8
- * Usage:
9
- * ```jsx
10
- * <FontSizedSvg>
11
- * <svg width="100%" height="100%" viewBox="...">
12
- * <path d="..." />
13
- * </svg>
14
- * </FontSizedSvg>
15
- * ```
16
- *
17
- * Notes:
18
- * - The wrapped SVG should use width="100%" and height="100%" to fill the container
19
- * - This ensures SVG icons match the current text size without additional styling
20
- * - Useful for inline icons that should respect the parent's font-size
21
- */
22
-
23
- import { withPropsStyle } from "../props_composition/with_props_style.js";
24
-
25
- import.meta.css = /* css */ `
26
- .navi_font_sized_svg {
27
- display: flex;
28
- width: 1em;
29
- height: 1em;
30
- flex-shrink: 0;
31
- align-items: center;
32
- justify-self: center;
33
- line-height: 1em;
34
- }
35
- `;
36
-
37
- export const FontSizedSvg = ({
38
- width = "1em",
39
- height = "1em",
40
- style,
41
- children,
42
- ...props
43
- }) => {
44
- return (
45
- <span
46
- {...props}
47
- className="navi_font_sized_svg"
48
- style={withPropsStyle(
49
- {
50
- width: width === "1em" ? undefined : width,
51
- height: height === "1em" ? undefined : height,
52
- },
53
- style,
54
- )}
55
- >
56
- {children}
57
- </span>
58
- );
59
- };
@@ -1,21 +0,0 @@
1
- import { FontSizedSvg } from "./font_sized_svg.jsx";
2
-
3
- export const IconAndText = ({ icon, children, ...rest }) => {
4
- if (typeof icon === "function") icon = icon({});
5
-
6
- return (
7
- <span
8
- className="icon_and_text"
9
- {...rest}
10
- style={{
11
- display: "flex",
12
- alignItems: "center",
13
- gap: "0.1em",
14
- ...rest.style,
15
- }}
16
- >
17
- <FontSizedSvg className="icon">{icon}</FontSizedSvg>
18
- <span className="text">{children}</span>
19
- </span>
20
- );
21
- };
@@ -1,105 +0,0 @@
1
- /**
2
- * SVGComposition Component
3
- *
4
- * Creates composite SVGs by combining independent SVG elements with masking.
5
- *
6
- * This component solves the challenge of combining independently created SVGs into
7
- * a single visual composition. Each SVG can have its own coordinate system, viewBox,
8
- * and styling, allowing for maximum reusability of individual icons or graphics.
9
- *
10
- * When overlaying SVGs, each subsequent overlay "cuts out" its portion from the base SVG,
11
- * creating a seamless integration where SVGs appear to interact with each other visually.
12
- *
13
- * Key benefits:
14
- * - Maintains each SVG's independence - use them individually elsewhere
15
- * - Handles different viewBox dimensions automatically
16
- * - Works with any SVG components regardless of internal implementation
17
- * - Supports unlimited overlay elements
18
- * - Creates proper masking between elements for visual integration
19
- *
20
- * Usage example combining two independent icon components:
21
- * ```jsx
22
- * <SVGMaskOverlay viewBox="0 0 24 24">
23
- * <DatabaseSvg />
24
- * <svg x="12" y="12" width="16" height="16" overflow="visible">
25
- * <PlusSvg />
26
- * </svg>
27
- * </SVGMaskOverlay>
28
- * ```
29
- *
30
- * @param {Object} props - Component properties
31
- * @param {string} props.viewBox - The main viewBox for the composition (required)
32
- * @param {ReactNode[]} props.children - SVG elements (first is base, rest are overlays)
33
- * @returns {ReactElement} A composed SVG with all elements properly masked
34
- */
35
-
36
- import { cloneElement } from "preact";
37
-
38
- import.meta.css = /* css */ `
39
- .svg_mask_content * {
40
- fill: black !important;
41
- stroke: black !important;
42
- fill-opacity: 1 !important;
43
- stroke-opacity: 1 !important;
44
- color: black !important;
45
- opacity: 1 !important;
46
- }
47
- `;
48
-
49
- export const SVGMaskOverlay = ({ viewBox, children }) => {
50
- if (!Array.isArray(children)) {
51
- return children;
52
- }
53
- if (children.length === 1) {
54
- return children[0];
55
- }
56
- if (!viewBox) {
57
- console.error("SVGComposition requires an explicit viewBox");
58
- return null;
59
- }
60
-
61
- // First SVG is the base, all others are overlays
62
- const [baseSvg, ...overlaySvgs] = children;
63
-
64
- // Generate unique ID for this instance
65
- const instanceId = `svgmo-${Math.random().toString(36).slice(2, 9)}`;
66
-
67
- // Create nested masked elements
68
- let maskedElement = baseSvg;
69
-
70
- // Apply each mask in sequence
71
- overlaySvgs.forEach((overlaySvg, index) => {
72
- const maskId = `mask-${instanceId}-${index}`;
73
- maskedElement = <g mask={`url(#${maskId})`}>{maskedElement}</g>;
74
- });
75
-
76
- return (
77
- <svg viewBox={viewBox} width="100%" height="100%">
78
- <defs>
79
- {/* Define masks that respect position */}
80
- {overlaySvgs.map((overlaySvg, index) => {
81
- const maskId = `mask-${instanceId}-${index}`;
82
-
83
- // IMPORTANT: clone the overlay SVG exactly as is, just add the mask class
84
- return (
85
- <mask id={maskId} key={maskId}>
86
- {/* White background makes everything visible by default */}
87
- <rect width="100%" height="100%" fill="white" />
88
-
89
- {/* EXACT CLONE of the overlay SVG */}
90
- {cloneElement(overlaySvg, {
91
- className: "svg_mask_content", // Apply styling to make it black
92
- })}
93
- </mask>
94
- );
95
- })}
96
- </defs>
97
-
98
- {/* Base SVG with all masks applied */}
99
- {maskedElement}
100
-
101
- {/* Render all overlays */}
102
- {overlaySvgs}
103
- </svg>
104
- );
105
- };
@@ -1,506 +0,0 @@
1
- import {
2
- createDragToMoveGestureController,
3
- createPubSub,
4
- dragAfterThreshold,
5
- getDropTargetInfo,
6
- getScrollContainer,
7
- stickyAsRelativeCoords,
8
- } from "@jsenv/dom";
9
- import { createContext } from "preact";
10
- import { forwardRef } from "preact/compat";
11
- import { useMemo, useState } from "preact/hooks";
12
-
13
- import { useStableCallback } from "../../use_stable_callback.js";
14
- import { Z_INDEX_CELL_FOREGROUND, Z_INDEX_DROP_PREVIEW } from "../z_indexes.js";
15
-
16
- const DEBUG_VISUAL = false;
17
-
18
- import.meta.css = /* css */ `
19
- .navi_table_drag_clone_container {
20
- position: absolute;
21
- left: var(--table-visual-left);
22
- top: var(--table-visual-top);
23
- width: var(--table-visual-width);
24
- height: var(--table-visual-height);
25
- /* background: rgba(0, 0, 0, 0.5); */
26
- }
27
-
28
- .navi_table_cell[data-grabbed]::before,
29
- .navi_table_cell[data-grabbed]::after {
30
- box-shadow: none !important;
31
- }
32
-
33
- /* We preprend ".navi_table_container" to ensure it propertly overrides */
34
- .navi_table_drag_clone_container .navi_table_cell {
35
- opacity: ${DEBUG_VISUAL ? 0.5 : 0};
36
- }
37
-
38
- .navi_table_drag_clone_container .navi_table_cell[data-grabbed] {
39
- opacity: 0.7;
40
- }
41
-
42
- .navi_table_drag_clone_container .navi_table_cell_sticky_frontier {
43
- opacity: 0;
44
- }
45
-
46
- .navi_table_drag_clone_container .navi_table_cell[data-sticky-left],
47
- .navi_table_drag_clone_container .navi_table_cell[data-sticky-top] {
48
- position: relative;
49
- }
50
-
51
- .navi_table_cell_foreground {
52
- pointer-events: none;
53
- position: absolute;
54
- inset: 0;
55
- background: lightgrey;
56
- opacity: 0;
57
- z-index: ${Z_INDEX_CELL_FOREGROUND};
58
- }
59
- .navi_table_cell[data-first-row] .navi_table_cell_foreground {
60
- background-color: grey;
61
- }
62
- .navi_table_cell_foreground[data-visible] {
63
- opacity: 1;
64
- }
65
-
66
- .navi_table_drag_clone_container .navi_table_cell_foreground {
67
- opacity: 1;
68
- background-color: rgba(255, 255, 255, 0.2);
69
- backdrop-filter: blur(10px);
70
- }
71
- .navi_table_drag_clone_container
72
- .navi_table_cell[data-first-row][data-grabbed] {
73
- opacity: 1;
74
- }
75
- .navi_table_drag_clone_container
76
- .navi_table_cell[data-first-row]
77
- .navi_table_cell_foreground {
78
- opacity: 0;
79
- }
80
-
81
- .navi_table_column_drop_preview {
82
- position: absolute;
83
- left: var(--column-left);
84
- top: var(--column-top);
85
- width: var(--column-width);
86
- height: var(--column-height);
87
- pointer-events: none;
88
- z-index: ${Z_INDEX_DROP_PREVIEW};
89
- /* Invisible container - just for positioning */
90
- background: transparent;
91
- border: none;
92
- }
93
-
94
- .navi_table_column_drop_preview_line {
95
- position: absolute;
96
- top: 0;
97
- bottom: 0;
98
- width: 4px;
99
- background: rgba(0, 0, 255, 0.5);
100
- opacity: 0;
101
- left: 0; /* Default: left edge for dropping before */
102
- transform: translateX(-50%);
103
- }
104
- .navi_table_column_drop_preview[data-after]
105
- .navi_table_column_drop_preview_line {
106
- left: 100%; /* Right edge for dropping after */
107
- }
108
- .navi_table_column_drop_preview[data-visible]
109
- .navi_table_column_drop_preview_line {
110
- opacity: 1;
111
- }
112
-
113
- .navi_table_column_drop_preview .arrow_positioner {
114
- position: absolute;
115
- left: 0; /* Default: left edge for dropping before */
116
- display: flex;
117
- opacity: 0;
118
- transform: translateX(-50%);
119
- color: rgba(0, 0, 255, 0.5);
120
- }
121
- .navi_table_column_drop_preview[data-after] .arrow_positioner {
122
- left: 100%; /* Right edge for dropping after */
123
- }
124
- .navi_table_column_drop_preview[data-visible] .arrow_positioner {
125
- opacity: 1;
126
- }
127
- .navi_table_column_drop_preview .arrow_positioner[data-top] {
128
- top: -10px;
129
- }
130
- .navi_table_column_drop_preview .arrow_positioner[data-bottom] {
131
- bottom: -10px;
132
- }
133
- .arrow_positioner svg {
134
- width: 10px;
135
- height: 10px;
136
- }
137
- `;
138
-
139
- export const TableDragContext = createContext();
140
- export const useTableDragContextValue = ({
141
- tableDragCloneContainerRef,
142
- tableColumnDropPreviewRef,
143
- columns,
144
- setColumnOrder,
145
- canChangeColumnOrder,
146
- }) => {
147
- setColumnOrder = useStableCallback(setColumnOrder);
148
-
149
- const [grabTarget, setGrabTarget] = useState(null);
150
- const grabColumn = (columnIndex) => {
151
- setGrabTarget(`column:${columnIndex}`);
152
- };
153
- const releaseColumn = (columnIndex, newColumnIndex) => {
154
- setGrabTarget(null);
155
- if (columnIndex === newColumnIndex) {
156
- return;
157
- }
158
- const columnIds = columns.map((col) => col.id);
159
- const columnIdsWithNewOrder = moveItem(
160
- columnIds,
161
- columnIndex,
162
- newColumnIndex,
163
- );
164
- setColumnOrder(columnIdsWithNewOrder);
165
- };
166
-
167
- return useMemo(() => {
168
- return {
169
- tableDragCloneContainerRef,
170
- tableColumnDropPreviewRef,
171
- grabTarget,
172
- grabColumn,
173
- releaseColumn,
174
- setColumnOrder,
175
- canChangeColumnOrder,
176
- };
177
- }, [grabTarget, canChangeColumnOrder]);
178
- };
179
- const moveItem = (array, indexA, indexB) => {
180
- const newArray = [];
181
- const movedItem = array[indexA];
182
- const movingRight = indexA < indexB;
183
-
184
- for (let i = 0; i < array.length; i++) {
185
- if (movingRight) {
186
- // Moving right: add target first, then moved item after
187
- if (i !== indexA) {
188
- newArray.push(array[i]);
189
- }
190
- if (i === indexB) {
191
- newArray.push(movedItem);
192
- }
193
- } else {
194
- // Moving left: add moved item first, then target after
195
- if (i === indexB) {
196
- newArray.push(movedItem);
197
- }
198
- if (i !== indexA) {
199
- newArray.push(array[i]);
200
- }
201
- }
202
- }
203
- return newArray;
204
- };
205
- export const swapItem = (array, indexA, indexB) => {
206
- const newArray = [];
207
- const itemAtPositionA = array[indexA];
208
- const itemAtPositionB = array[indexB];
209
- for (let i = 0; i < array.length; i++) {
210
- if (i === indexB) {
211
- // At the new position, put the dragged column
212
- newArray.push(itemAtPositionA);
213
- continue;
214
- }
215
- if (i === indexA) {
216
- // At the old position, put what was at the new position
217
- newArray.push(itemAtPositionB);
218
- continue;
219
- }
220
- // Everything else stays the same
221
- newArray.push(array[i]);
222
- }
223
- return newArray;
224
- };
225
-
226
- export const TableDragCloneContainer = forwardRef((props, ref) => {
227
- const { tableId } = props;
228
-
229
- return (
230
- <div
231
- ref={ref}
232
- className="navi_table_drag_clone_container"
233
- data-overlay-for={tableId}
234
- ></div>
235
- );
236
- });
237
- export const TableColumnDropPreview = forwardRef((props, ref) => {
238
- return (
239
- <div ref={ref} className="navi_table_column_drop_preview">
240
- <div className="arrow_positioner" data-top="">
241
- {/* This is an arrow pointing down */}
242
- <svg fill="currentColor" viewBox="0 0 30.727 30.727">
243
- <path
244
- d="M29.994,10.183L15.363,24.812L0.733,10.184c-0.977-0.978-0.977-2.561,0-3.536c0.977-0.977,2.559-0.976,3.536,0
245
- l11.095,11.093L26.461,6.647c0.977-0.976,2.559-0.976,3.535,0C30.971,7.624,30.971,9.206,29.994,10.183z"
246
- />
247
- </svg>
248
- </div>
249
- <div className="navi_table_column_drop_preview_line"></div>
250
- <div className="arrow_positioner" data-bottom="">
251
- {/* This is an arrow pointing up */}
252
- <svg fill="currentColor" viewBox="0 0 30.727 30.727">
253
- <path
254
- d="M29.994,20.544L15.363,5.915L0.733,20.543c-0.977,0.978-0.977,2.561,0,3.536c0.977,0.977,2.559,0.976,3.536,0
255
- l11.095-11.093L26.461,24.08c0.977,0.976,2.559,0.976,3.535,0C30.971,23.103,30.971,21.521,29.994,20.544z"
256
- />
257
- </svg>
258
- </div>
259
- </div>
260
- );
261
- });
262
-
263
- export const initDragTableColumnViaPointer = (
264
- pointerdownEvent,
265
- { tableDragCloneContainer, dropPreview, onGrab, onDrag, onRelease },
266
- ) => {
267
- dragAfterThreshold(pointerdownEvent, () => {
268
- const [teardown, addTeardown] = createPubSub();
269
-
270
- const tableCell = pointerdownEvent.target.closest(".navi_table_cell");
271
- const table = tableCell.closest(".navi_table");
272
- const columnIndex = Array.from(tableCell.parentNode.children).indexOf(
273
- tableCell,
274
- );
275
-
276
- // Track the drop target column index (starts as current column)
277
- let dropColumnIndex = columnIndex;
278
-
279
- const tableClone = table.cloneNode(true);
280
- // ensure [data-drag-obstacle] inside the table clone are ignored
281
- tableClone.setAttribute("data-drag-ignore", "");
282
-
283
- // Scale down the table clone and set transform origin to mouse grab point
284
- // const tableRect = table.getBoundingClientRect();
285
- // const mouseX = mousedownEvent.clientX - tableRect.left;
286
- // const mouseY = mousedownEvent.clientY - tableRect.top;
287
- // tableClone.style.transform = "scale(1.2)";
288
- // tableClone.style.transformOrigin = `${mouseX}px ${mouseY}px`;
289
-
290
- update_sticky_elements: {
291
- // In the table clone we need to convert sticky elements to position: relative
292
- // with calculated offsets that match their appearance in the original context
293
- const scrollContainer = getScrollContainer(table);
294
-
295
- // important: only on cells, not on <col> nor <tr>
296
- const originalStickyCells = table.querySelectorAll(
297
- ".navi_table_cell[data-sticky-left], .navi_table_cell[data-sticky-top]",
298
- );
299
- const cloneStickyCells = tableClone.querySelectorAll(
300
- ".navi_table_cell[data-sticky-left], .navi_table_cell[data-sticky-top]",
301
- );
302
-
303
- originalStickyCells.forEach((originalCell, index) => {
304
- const cloneCell = cloneStickyCells[index];
305
- const relativePosition = stickyAsRelativeCoords(
306
- originalCell,
307
- // Our clone is absolutely positioned on top of <table />
308
- // So we need the sticky position relative to <table />
309
- table,
310
- {
311
- scrollContainer,
312
- },
313
- );
314
- if (relativePosition) {
315
- const [relativeLeft, relativeTop] = relativePosition;
316
- cloneCell.style.position = "relative";
317
- if (relativeLeft !== undefined) {
318
- cloneCell.style.left = `${relativeLeft}px`;
319
- }
320
- if (relativeTop !== undefined) {
321
- cloneCell.style.top = `${relativeTop}px`;
322
- }
323
- }
324
- });
325
- }
326
-
327
- sync_data_grabbed: {
328
- // ensure [data-grabbed] are present in the table clone
329
- // we could retry on "sync_attributes" but we want to be sure it's done asap to prevent table from being displayed at all
330
- // I fear without this we might have an intermediate step where the table column clone is not visible
331
- // as [data-grabbed] are not set
332
- // Would not be a problem but this ensure we see exactly the table clone right away preventing any possibility
333
- // of visual glitches
334
- const tableCloneCells = tableClone.querySelectorAll(".navi_table_cell");
335
- tableCloneCells.forEach((cellClone) => {
336
- const cellColumnIndex = Array.from(
337
- cellClone.parentNode.children,
338
- ).indexOf(cellClone);
339
- if (cellColumnIndex === columnIndex) {
340
- cellClone.setAttribute("data-grabbed", "");
341
- }
342
- });
343
- }
344
-
345
- append_in_dom: {
346
- tableDragCloneContainer.appendChild(tableClone);
347
- addTeardown(() => {
348
- tableClone.remove();
349
- });
350
- }
351
-
352
- sync_attributes: {
353
- // Sync attribute changes from original table to clone
354
- // This is used to:
355
- // - handle table cells being selected as result of mousedown on the <th />
356
- // - nothing else is supposed to change in the original <table /> during the drag gesture
357
- const syncTableAttributes = createTableAttributeSync(table, tableClone);
358
- addTeardown(() => {
359
- syncTableAttributes.disconnect();
360
- });
361
- }
362
-
363
- const colgroup = table.querySelector(".navi_colgroup");
364
- const colElements = Array.from(colgroup.children);
365
-
366
- const col = colElements[columnIndex];
367
- const colgroupClone = tableClone.querySelector(".navi_colgroup");
368
- const colClone = colgroupClone.children[columnIndex];
369
- const dragToMoveGestureController = createDragToMoveGestureController({
370
- name: "move-column",
371
- direction: { x: true },
372
- threshold: 0,
373
- onGrab,
374
- onDrag: (gestureInfo) => {
375
- onDrag?.(gestureInfo, dropColumnIndex);
376
- },
377
- resetPositionAfterRelease: !DEBUG_VISUAL,
378
- onRelease: (gestureInfo) => {
379
- if (!DEBUG_VISUAL) {
380
- teardown();
381
- }
382
- onRelease?.(gestureInfo, dropColumnIndex);
383
- },
384
- });
385
- const dragToMoveGesture = dragToMoveGestureController.grabViaPointer(
386
- pointerdownEvent,
387
- {
388
- element: colClone,
389
- referenceElement: col,
390
- elementToMove: tableClone,
391
- },
392
- );
393
-
394
- drop_preview: {
395
- // Get all column elements for drop target detection
396
- const dropCandidateElements = colElements.filter(
397
- (col) =>
398
- !(col.getAttribute("data-drag-obstacle") || "").includes(
399
- "move-column",
400
- ),
401
- );
402
-
403
- const updateDropTarget = (dropTargetInfo) => {
404
- const targetColumn = dropTargetInfo.element;
405
- const targetColumnIndex = colElements.indexOf(targetColumn);
406
- dropColumnIndex = targetColumnIndex;
407
- if (dropColumnIndex === columnIndex) {
408
- dropPreview.removeAttribute("data-visible");
409
- return;
410
- }
411
- // Position the invisible container to match the target column
412
- const { left, top, width, height } =
413
- targetColumn.getBoundingClientRect();
414
- dropPreview.style.setProperty("--column-left", `${left}px`);
415
- dropPreview.style.setProperty("--column-top", `${top}px`);
416
- dropPreview.style.setProperty("--column-width", `${width}px`);
417
- dropPreview.style.setProperty("--column-height", `${height}px`);
418
- // Set data-after attribute to control line position via CSS
419
- if (dropColumnIndex > columnIndex) {
420
- // Dropping after: CSS will position line at right edge (100%)
421
- dropPreview.setAttribute("data-after", "");
422
- } else {
423
- // Dropping before: CSS will position line at left edge (0%)
424
- dropPreview.removeAttribute("data-after");
425
- }
426
- dropPreview.setAttribute("data-drop-column-index", dropColumnIndex);
427
- dropPreview.setAttribute("data-visible", "");
428
- };
429
-
430
- dragToMoveGesture.addDragCallback((gestureInfo) => {
431
- const dropTargetInfo = getDropTargetInfo(
432
- gestureInfo,
433
- dropCandidateElements,
434
- );
435
- if (!dropTargetInfo) {
436
- dropPreview.removeAttribute("data-visible");
437
- return;
438
- }
439
- updateDropTarget(dropTargetInfo);
440
- });
441
- dragToMoveGesture.addReleaseCallback(() => {
442
- dropPreview.removeAttribute("data-visible");
443
- });
444
- }
445
-
446
- return dragToMoveGesture;
447
- });
448
- };
449
-
450
- /**
451
- * Creates a MutationObserver that syncs attribute changes from original table to clone
452
- * @param {HTMLElement} table - The original table element
453
- * @param {HTMLElement} cloneTable - The cloned table element
454
- * @returns {MutationObserver} The observer instance with disconnect method
455
- */
456
- const createTableAttributeSync = (table, tableClone) => {
457
- // Create a map to quickly find corresponding elements in the clone
458
- const createElementMap = () => {
459
- const map = new Map();
460
- const cells = table.querySelectorAll(".navi_table_cell");
461
- const cellClones = tableClone.querySelectorAll(".navi_table_cell");
462
- for (let i = 0; i < cells.length; i++) {
463
- if (cellClones[i]) {
464
- map.set(cells[i], cellClones[i]);
465
- }
466
- }
467
- return map;
468
- };
469
-
470
- const elementMap = createElementMap();
471
- const observer = new MutationObserver((mutations) => {
472
- mutations.forEach((mutation) => {
473
- if (mutation.type === "attributes") {
474
- const originalElement = mutation.target;
475
- const cloneElement = elementMap.get(originalElement);
476
-
477
- if (cloneElement) {
478
- const attributeName = mutation.attributeName;
479
- if (attributeName === "style") {
480
- return;
481
- }
482
-
483
- // Sync the attribute change to the clone
484
- if (originalElement.hasAttribute(attributeName)) {
485
- const attributeValue = originalElement.getAttribute(attributeName);
486
- cloneElement.setAttribute(attributeName, attributeValue);
487
- } else {
488
- cloneElement.removeAttribute(attributeName);
489
- }
490
- }
491
- }
492
- });
493
- });
494
-
495
- // Observe attribute changes on all table cells
496
- const cellsToObserve = table.querySelectorAll(".navi_table_cell");
497
- cellsToObserve.forEach((cell) => {
498
- observer.observe(cell, {
499
- attributes: true,
500
- attributeOldValue: false,
501
- subtree: false,
502
- });
503
- });
504
-
505
- return observer;
506
- };