@jsenv/navi 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jsenv_navi.js +22959 -0
- package/index.js +66 -16
- package/package.json +23 -11
- package/src/actions.js +50 -26
- package/src/browser_integration/browser_integration.js +31 -6
- package/src/browser_integration/via_history.js +42 -9
- package/src/components/action_execution/render_actionable_component.jsx +6 -4
- package/src/components/action_execution/use_action.js +51 -282
- package/src/components/action_execution/use_execute_action.js +106 -92
- package/src/components/action_execution/use_run_on_mount.js +9 -0
- package/src/components/action_renderer.jsx +21 -32
- package/src/components/demos/0_button_demo.html +574 -103
- package/src/components/demos/10_column_reordering_debug.html +277 -0
- package/src/components/demos/11_table_selection_debug.html +432 -0
- package/src/components/demos/1_checkbox_demo.html +579 -202
- package/src/components/demos/2_input_textual_demo.html +81 -138
- package/src/components/demos/3_radio_demo.html +0 -2
- package/src/components/demos/4_select_demo.html +19 -23
- package/src/components/demos/6_tablist_demo.html +77 -0
- package/src/components/demos/7_table_selection_demo.html +176 -0
- package/src/components/demos/8_table_fixed_headers_demo.html +584 -0
- package/src/components/demos/9_table_column_drag_demo.html +325 -0
- package/src/components/demos/action/0_button_demo.html +2 -4
- package/src/components/demos/action/1_input_text_demo.html +643 -222
- package/src/components/demos/action/3_details_demo.html +146 -115
- package/src/components/demos/action/4_input_checkbox_demo.html +442 -322
- package/src/components/demos/action/5_input_checkbox_state_demo.html +270 -0
- package/src/components/demos/action/6_checkbox_list_demo.html +304 -72
- package/src/components/demos/action/7_radio_list_demo.html +310 -170
- package/src/components/demos/action/{8_editable_text_demo.html → 8_editable_demo.html} +65 -76
- package/src/components/demos/action/9_link_demo.html +84 -62
- package/src/components/demos/ui_transition/0_action_renderer_ui_transition_demo.html +695 -0
- package/src/components/demos/ui_transition/1_nested_ui_transition_demo.html +429 -0
- package/src/components/demos/ui_transition/2_height_transition_test.html +295 -0
- package/src/components/details/details.jsx +62 -64
- package/src/components/edition/editable.jsx +186 -0
- package/src/components/field/README.md +247 -0
- package/src/components/{input → field}/button.jsx +151 -130
- package/src/components/field/checkbox_list.jsx +184 -0
- package/src/components/{collect_form_element_values.js → field/collect_form_element_values.js} +7 -4
- package/src/components/{input → field}/field_css.js +4 -1
- package/src/components/field/form.jsx +211 -0
- package/src/components/{input → field}/input.jsx +1 -0
- package/src/components/{input → field}/input_checkbox.jsx +132 -155
- package/src/components/{input → field}/input_radio.jsx +135 -46
- package/src/components/field/input_textual.jsx +418 -0
- package/src/components/field/label.jsx +32 -0
- package/src/components/field/radio_list.jsx +182 -0
- package/src/components/{input → field}/select.jsx +17 -32
- package/src/components/field/use_action_events.js +132 -0
- package/src/components/field/use_form_events.js +55 -0
- package/src/components/field/use_ui_state_controller.js +506 -0
- package/src/components/item_tracker/README.md +461 -0
- package/src/components/item_tracker/use_isolated_item_tracker.jsx +209 -0
- package/src/components/item_tracker/use_isolated_item_tracker_demo.html +148 -0
- package/src/components/item_tracker/use_isolated_item_tracker_demo.jsx +460 -0
- package/src/components/item_tracker/use_item_tracker.jsx +143 -0
- package/src/components/item_tracker/use_item_tracker_demo.html +207 -0
- package/src/components/item_tracker/use_item_tracker_demo.jsx +216 -0
- package/src/components/keyboard_shortcuts/active_keyboard_shortcuts.jsx +87 -0
- package/src/components/keyboard_shortcuts/aria_key_shortcuts.js +61 -0
- package/src/components/keyboard_shortcuts/keyboard_key_meta.js +17 -0
- package/src/components/keyboard_shortcuts/keyboard_shortcuts.js +371 -0
- package/src/components/link/link.jsx +65 -102
- package/src/components/link/link_with_icon.jsx +52 -0
- package/src/components/loader/loader_background.jsx +85 -64
- package/src/components/loader/rectangle_loading.jsx +38 -19
- package/src/components/route.jsx +8 -4
- package/src/components/selection/selection.jsx +1583 -0
- package/src/components/svg/font_sized_svg.jsx +45 -0
- package/src/components/svg/icon_and_text.jsx +21 -0
- package/src/components/svg/svg_mask_overlay.jsx +105 -0
- package/src/components/table/drag/table_drag.jsx +506 -0
- package/src/components/table/resize/table_resize.jsx +650 -0
- package/src/components/table/resize/table_size.js +43 -0
- package/src/components/table/selection/table_selection.js +106 -0
- package/src/components/table/selection/table_selection.jsx +203 -0
- package/src/components/table/sticky/sticky_group.js +354 -0
- package/src/components/table/sticky/table_sticky.js +25 -0
- package/src/components/table/sticky/table_sticky.jsx +501 -0
- package/src/components/table/table.jsx +721 -0
- package/src/components/table/table_css.js +211 -0
- package/src/components/table/table_ui.jsx +49 -0
- package/src/components/table/use_cells_and_columns.js +90 -0
- package/src/components/table/use_object_array_to_cells.js +46 -0
- package/src/components/table/z_indexes.js +23 -0
- package/src/components/tablist/tablist.jsx +99 -0
- package/src/components/text/overflow.jsx +15 -0
- package/src/components/text/text_and_count.jsx +28 -0
- package/src/components/ui_transition.jsx +128 -0
- package/src/components/use_auto_focus.js +58 -7
- package/src/components/use_batch_during_render.js +33 -0
- package/src/components/use_debounce_true.js +7 -7
- package/src/components/use_dependencies_diff.js +35 -0
- package/src/components/use_focus_group.js +4 -3
- package/src/components/use_initial_value.js +8 -34
- package/src/components/use_signal_sync.js +1 -1
- package/src/components/use_stable_callback.js +68 -0
- package/src/components/use_state_array.js +16 -9
- package/src/docs/actions.md +22 -0
- package/src/notes.md +33 -12
- package/src/route/route.js +97 -47
- package/src/store/resource_graph.js +2 -1
- package/src/store/tests/{resource_graph_dependencies.test.js → resource_graph_dependencies.test_manual.js} +13 -13
- package/src/utils/is_signal.js +20 -0
- package/src/utils/stringify_for_display.js +4 -23
- package/src/validation/constraints/confirm_constraint.js +14 -0
- package/src/validation/constraints/create_unique_value_constraint.js +27 -0
- package/src/validation/constraints/native_constraints.js +313 -0
- package/src/validation/constraints/readonly_constraint.js +36 -0
- package/src/validation/constraints/single_space_constraint.js +13 -0
- package/src/validation/custom_constraint_validation.js +599 -0
- package/src/validation/custom_message.js +18 -0
- package/src/validation/demos/browser_style.png +0 -0
- package/src/validation/demos/form_validation_demo.html +142 -0
- package/src/validation/demos/form_validation_demo_preact.html +87 -0
- package/src/validation/demos/form_validation_native_popover_demo.html +168 -0
- package/src/validation/demos/form_validation_vs_native_demo.html +172 -0
- package/src/validation/demos/validation_message_demo.html +203 -0
- package/src/validation/hooks/use_constraints.js +23 -0
- package/src/validation/hooks/use_custom_validation_ref.js +73 -0
- package/src/validation/hooks/use_validation_message.js +19 -0
- package/src/validation/validation_message.js +741 -0
- package/src/components/editable_text/editable_text.jsx +0 -96
- package/src/components/form.jsx +0 -144
- package/src/components/input/checkbox_list.jsx +0 -294
- package/src/components/input/field.jsx +0 -61
- package/src/components/input/input_textual.jsx +0 -338
- package/src/components/input/radio_list.jsx +0 -283
- package/src/components/input/use_form_event.js +0 -20
- package/src/components/input/use_on_change.js +0 -12
- package/src/components/selection/selection.js +0 -5
- package/src/components/selection/selection_context.jsx +0 -262
- package/src/components/shortcut/shortcut_context.jsx +0 -390
- package/src/components/use_action_events.js +0 -37
- package/src/utils/iterable_weak_set.js +0 -62
- /package/src/components/demos/action/{11_nested_shortcuts_demo.html → 11_nested_shortcuts_demo.xhtml} +0 -0
- /package/src/components/{shortcut → keyboard_shortcuts}/os.js +0 -0
- /package/src/route/{route.test.html → route.xtest.html} +0 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
// TODO: sticky left/top frontier should likely use "followPosition"
|
|
2
|
+
// to be properly position in absolute in the document body
|
|
3
|
+
// because otherwise they can't properly follow table as it scrolls
|
|
4
|
+
// (they can't be sticky, they can't react to scroll, so they have to be absolute in the document body)
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
createDragToMoveGestureController,
|
|
8
|
+
getDropTargetInfo,
|
|
9
|
+
getScrollContainer,
|
|
10
|
+
} from "@jsenv/dom";
|
|
11
|
+
import { useContext, useRef } from "preact/hooks";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
Z_INDEX_STICKY_COLUMN,
|
|
15
|
+
Z_INDEX_STICKY_CORNER,
|
|
16
|
+
Z_INDEX_STICKY_FRONTIER_BACKDROP,
|
|
17
|
+
Z_INDEX_STICKY_FRONTIER_GHOST,
|
|
18
|
+
Z_INDEX_STICKY_FRONTIER_PREVIEW,
|
|
19
|
+
Z_INDEX_STICKY_ROW,
|
|
20
|
+
} from "../z_indexes.js";
|
|
21
|
+
import { TableStickyContext } from "./table_sticky.js";
|
|
22
|
+
|
|
23
|
+
import.meta.css = /* css */ `
|
|
24
|
+
body {
|
|
25
|
+
--sticky-frontier-color: #c0c0c0;
|
|
26
|
+
--sticky-frontier-size: 12px;
|
|
27
|
+
--sticky-frontier-ghost-size: 8px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.navi_table_cell[data-sticky-top] {
|
|
31
|
+
position: sticky;
|
|
32
|
+
top: var(--sticky-group-item-top, 0);
|
|
33
|
+
z-index: ${Z_INDEX_STICKY_ROW};
|
|
34
|
+
}
|
|
35
|
+
.navi_table_cell[data-sticky-left] {
|
|
36
|
+
position: sticky;
|
|
37
|
+
left: var(--sticky-group-item-left, 0);
|
|
38
|
+
z-index: ${Z_INDEX_STICKY_COLUMN};
|
|
39
|
+
}
|
|
40
|
+
.navi_table_cell[data-sticky-left][data-sticky-top] {
|
|
41
|
+
position: sticky;
|
|
42
|
+
top: var(--sticky-group-item-top, 0);
|
|
43
|
+
left: var(--sticky-group-item-left, 0);
|
|
44
|
+
z-index: ${Z_INDEX_STICKY_CORNER};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Useful because drag gesture will read this value to detect <col>, <tr> virtual position */
|
|
48
|
+
.navi_col {
|
|
49
|
+
left: var(--sticky-group-item-left, 0);
|
|
50
|
+
}
|
|
51
|
+
.navi_tr {
|
|
52
|
+
top: var(--sticky-group-item-top, 0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.navi_table_sticky_frontier {
|
|
56
|
+
position: absolute;
|
|
57
|
+
cursor: grab;
|
|
58
|
+
pointer-events: auto;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.navi_table_sticky_frontier[data-left] {
|
|
62
|
+
left: calc(var(--table-visual-left) + var(--sticky-group-left));
|
|
63
|
+
width: var(--sticky-frontier-size);
|
|
64
|
+
top: calc(var(--table-visual-top) + var(--sticky-group-top));
|
|
65
|
+
height: calc(var(--table-visual-height) - var(--sticky-group-top));
|
|
66
|
+
background: linear-gradient(
|
|
67
|
+
to right,
|
|
68
|
+
rgba(0, 0, 0, 0.1) 0%,
|
|
69
|
+
rgba(0, 0, 0, 0) 100%
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.navi_table_sticky_frontier[data-top] {
|
|
74
|
+
left: calc(var(--table-visual-left) + var(--sticky-group-left));
|
|
75
|
+
width: calc(var(--table-visual-width) - var(--sticky-group-left));
|
|
76
|
+
top: calc(var(--table-visual-top) + var(--sticky-group-top));
|
|
77
|
+
height: var(--sticky-frontier-size);
|
|
78
|
+
background: linear-gradient(
|
|
79
|
+
to bottom,
|
|
80
|
+
rgba(0, 0, 0, 0.1) 0%,
|
|
81
|
+
rgba(0, 0, 0, 0) 100%
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.navi_table_sticky_frontier_ghost,
|
|
86
|
+
.navi_table_sticky_frontier_preview {
|
|
87
|
+
position: absolute;
|
|
88
|
+
pointer-events: none;
|
|
89
|
+
opacity: 0;
|
|
90
|
+
}
|
|
91
|
+
.navi_table_sticky_frontier_ghost {
|
|
92
|
+
z-index: ${Z_INDEX_STICKY_FRONTIER_GHOST};
|
|
93
|
+
background: rgba(68, 71, 70, 0.5);
|
|
94
|
+
}
|
|
95
|
+
.navi_table_sticky_frontier_preview {
|
|
96
|
+
z-index: ${Z_INDEX_STICKY_FRONTIER_PREVIEW};
|
|
97
|
+
background: rgba(56, 121, 200, 0.5);
|
|
98
|
+
}
|
|
99
|
+
.navi_table_sticky_frontier_ghost[data-visible],
|
|
100
|
+
.navi_table_sticky_frontier_preview[data-visible] {
|
|
101
|
+
opacity: 1;
|
|
102
|
+
}
|
|
103
|
+
.navi_table_sticky_frontier_ghost[data-left],
|
|
104
|
+
.navi_table_sticky_frontier_preview[data-left] {
|
|
105
|
+
top: 0;
|
|
106
|
+
width: var(--sticky-frontier-ghost-size);
|
|
107
|
+
height: var(--table-height, 100%);
|
|
108
|
+
}
|
|
109
|
+
.navi_table_sticky_frontier_ghost[data-left] {
|
|
110
|
+
left: var(--sticky-frontier-ghost-position, 0px);
|
|
111
|
+
}
|
|
112
|
+
.navi_table_sticky_frontier_preview[data-left] {
|
|
113
|
+
left: var(--sticky-frontier-preview-position, 0px);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.navi_table_sticky_frontier_ghost[data-top],
|
|
117
|
+
.navi_table_sticky_frontier_preview[data-top] {
|
|
118
|
+
left: 0;
|
|
119
|
+
width: var(--table-width, 100%);
|
|
120
|
+
height: var(--sticky-frontier-ghost-size);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.navi_table_sticky_frontier_ghost[data-top] {
|
|
124
|
+
top: var(--sticky-frontier-ghost-position, 0px);
|
|
125
|
+
}
|
|
126
|
+
.navi_table_sticky_frontier_preview[data-top] {
|
|
127
|
+
top: var(--sticky-frontier-preview-position, 0px);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Positioning adjustments for ::after pseudo-elements on cells adjacent to sticky frontiers */
|
|
131
|
+
/* These ensure selection and focus borders align with the ::before borders */
|
|
132
|
+
.navi_table[data-border-collapse]
|
|
133
|
+
.navi_table_cell[data-after-sticky-left-frontier]::after {
|
|
134
|
+
left: 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.navi_table[data-border-collapse]
|
|
138
|
+
.navi_table_cell[data-after-sticky-top-frontier]::after {
|
|
139
|
+
top: 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* Base borders for sticky cells (will be overridden by frontier rules) */
|
|
143
|
+
.navi_table[data-border-collapse] .navi_table_cell[data-sticky-left]::before {
|
|
144
|
+
box-shadow:
|
|
145
|
+
inset -1px 0 0 0 var(--border-color),
|
|
146
|
+
inset 0 -1px 0 0 var(--border-color);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.navi_table[data-border-collapse] .navi_table_cell[data-sticky-top]::before {
|
|
150
|
+
box-shadow:
|
|
151
|
+
inset -1px 0 0 0 var(--border-color),
|
|
152
|
+
inset 0 -1px 0 0 var(--border-color);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* Header row sticky cells need top border */
|
|
156
|
+
.navi_table[data-border-collapse]
|
|
157
|
+
.navi_table_cell[data-first-row][data-sticky-left]::before {
|
|
158
|
+
box-shadow:
|
|
159
|
+
inset 0 1px 0 0 var(--border-color),
|
|
160
|
+
inset -1px 0 0 0 var(--border-color),
|
|
161
|
+
inset 0 -1px 0 0 var(--border-color);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.navi_table[data-border-collapse]
|
|
165
|
+
.navi_table_cell[data-first-row][data-sticky-top]::before {
|
|
166
|
+
box-shadow:
|
|
167
|
+
inset 0 1px 0 0 var(--border-color),
|
|
168
|
+
inset -1px 0 0 0 var(--border-color),
|
|
169
|
+
inset 0 -1px 0 0 var(--border-color);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* First column sticky cells need left border */
|
|
173
|
+
.navi_table[data-border-collapse]
|
|
174
|
+
.navi_table_cell[data-first-column][data-sticky-left]::before {
|
|
175
|
+
box-shadow:
|
|
176
|
+
inset 1px 0 0 0 var(--border-color),
|
|
177
|
+
inset -1px 0 0 0 var(--border-color),
|
|
178
|
+
inset 0 -1px 0 0 var(--border-color);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.navi_table[data-border-collapse]
|
|
182
|
+
.navi_table_cell[data-first-column][data-sticky-top]::before {
|
|
183
|
+
box-shadow:
|
|
184
|
+
inset 1px 0 0 0 var(--border-color),
|
|
185
|
+
inset -1px 0 0 0 var(--border-color),
|
|
186
|
+
inset 0 -1px 0 0 var(--border-color);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* Header first column sticky cells get all four regular borders */
|
|
190
|
+
.navi_table[data-border-collapse]
|
|
191
|
+
.navi_table_cell[data-first-row][data-first-column][data-sticky-left]::before,
|
|
192
|
+
.navi_table[data-border-collapse]
|
|
193
|
+
.navi_table_cell[data-first-row][data-first-column][data-sticky-top]::before {
|
|
194
|
+
box-shadow:
|
|
195
|
+
inset 0 1px 0 0 var(--border-color),
|
|
196
|
+
inset 1px 0 0 0 var(--border-color),
|
|
197
|
+
inset -1px 0 0 0 var(--border-color),
|
|
198
|
+
inset 0 -1px 0 0 var(--border-color);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* Borders for cells immediately after sticky frontiers */
|
|
202
|
+
|
|
203
|
+
/* Left border for the column after sticky-x-frontier */
|
|
204
|
+
.navi_table[data-border-collapse]
|
|
205
|
+
.navi_table_cell[data-after-sticky-left-frontier]::before {
|
|
206
|
+
box-shadow:
|
|
207
|
+
inset 1px 0 0 0 var(--border-color),
|
|
208
|
+
inset -1px 0 0 0 var(--border-color),
|
|
209
|
+
inset 0 -1px 0 0 var(--border-color);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* Header cells after sticky-x-frontier also need top border (for border-collapse) */
|
|
213
|
+
.navi_table[data-border-collapse]
|
|
214
|
+
.navi_table_cell[data-first-row][data-after-sticky-left-frontier]::before {
|
|
215
|
+
box-shadow:
|
|
216
|
+
inset 0 1px 0 0 var(--border-color),
|
|
217
|
+
inset 1px 0 0 0 var(--border-color),
|
|
218
|
+
inset -1px 0 0 0 var(--border-color),
|
|
219
|
+
inset 0 -1px 0 0 var(--border-color);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* Top border for the row after sticky-y-frontier */
|
|
223
|
+
.navi_table[data-border-collapse]
|
|
224
|
+
.navi_table_cell[data-after-sticky-top-frontier]::before {
|
|
225
|
+
box-shadow:
|
|
226
|
+
inset 0 1px 0 0 var(--border-color),
|
|
227
|
+
inset -1px 0 0 0 var(--border-color),
|
|
228
|
+
inset 0 -1px 0 0 var(--border-color);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* Header cells after sticky-y-frontier also need left border (for border-collapse) */
|
|
232
|
+
.navi_table[data-border-collapse]
|
|
233
|
+
.navi_table_cell[data-first-row][data-after-sticky-top-frontier]::before {
|
|
234
|
+
box-shadow:
|
|
235
|
+
inset 0 1px 0 0 var(--border-color),
|
|
236
|
+
inset 1px 0 0 0 var(--border-color),
|
|
237
|
+
inset -1px 0 0 0 var(--border-color),
|
|
238
|
+
inset 0 -1px 0 0 var(--border-color);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/* First column cells after sticky-y-frontier need all four borders (for border-collapse) */
|
|
242
|
+
.navi_table[data-border-collapse]
|
|
243
|
+
.navi_table_cell[data-first-column][data-after-sticky-top-frontier]::before {
|
|
244
|
+
box-shadow:
|
|
245
|
+
inset 0 1px 0 0 var(--border-color),
|
|
246
|
+
inset 1px 0 0 0 var(--border-color),
|
|
247
|
+
inset -1px 0 0 0 var(--border-color),
|
|
248
|
+
inset 0 -1px 0 0 var(--border-color);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* Corner case: cell after both sticky frontiers */
|
|
252
|
+
.navi_table[data-border-collapse]
|
|
253
|
+
.navi_table_cell[data-after-sticky-left-frontier][data-after-sticky-top-frontier]::before {
|
|
254
|
+
box-shadow:
|
|
255
|
+
inset 1px 0 0 0 var(--border-color),
|
|
256
|
+
inset 0 1px 0 0 var(--border-color),
|
|
257
|
+
inset -1px 0 0 0 var(--border-color),
|
|
258
|
+
inset 0 -1px 0 0 var(--border-color);
|
|
259
|
+
}
|
|
260
|
+
`;
|
|
261
|
+
|
|
262
|
+
export const TableStickyFrontier = ({ tableRef }) => {
|
|
263
|
+
const stickyLeftFrontierGhostRef = useRef();
|
|
264
|
+
const stickyLeftFrontierPreviewRef = useRef();
|
|
265
|
+
const stickyTopFrontierGhostRef = useRef();
|
|
266
|
+
const stickyTopFrontierPreviewRef = useRef();
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<>
|
|
270
|
+
<TableStickyLeftFrontier
|
|
271
|
+
tableRef={tableRef}
|
|
272
|
+
stickyLeftFrontierGhostRef={stickyLeftFrontierGhostRef}
|
|
273
|
+
stickyLeftFrontierPreviewRef={stickyLeftFrontierPreviewRef}
|
|
274
|
+
/>
|
|
275
|
+
<TableStickyTopFrontier
|
|
276
|
+
tableRef={tableRef}
|
|
277
|
+
stickyTopFrontierGhostRef={stickyTopFrontierGhostRef}
|
|
278
|
+
stickyTopFrontierPreviewRef={stickyTopFrontierPreviewRef}
|
|
279
|
+
/>
|
|
280
|
+
<div
|
|
281
|
+
ref={stickyLeftFrontierGhostRef}
|
|
282
|
+
className="navi_table_sticky_frontier_ghost"
|
|
283
|
+
data-left=""
|
|
284
|
+
></div>
|
|
285
|
+
<div
|
|
286
|
+
ref={stickyLeftFrontierPreviewRef}
|
|
287
|
+
className="navi_table_sticky_frontier_preview"
|
|
288
|
+
data-left=""
|
|
289
|
+
></div>
|
|
290
|
+
<div
|
|
291
|
+
ref={stickyTopFrontierGhostRef}
|
|
292
|
+
className="navi_table_sticky_frontier_ghost"
|
|
293
|
+
data-top=""
|
|
294
|
+
></div>
|
|
295
|
+
<div
|
|
296
|
+
ref={stickyTopFrontierPreviewRef}
|
|
297
|
+
className="navi_table_sticky_frontier_preview"
|
|
298
|
+
data-top=""
|
|
299
|
+
></div>
|
|
300
|
+
</>
|
|
301
|
+
);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const TableStickyLeftFrontier = ({
|
|
305
|
+
tableRef,
|
|
306
|
+
stickyLeftFrontierGhostRef,
|
|
307
|
+
stickyLeftFrontierPreviewRef,
|
|
308
|
+
}) => {
|
|
309
|
+
const { stickyLeftFrontierColumnIndex, onStickyLeftFrontierChange } =
|
|
310
|
+
useContext(TableStickyContext);
|
|
311
|
+
const canMoveFrontier = Boolean(onStickyLeftFrontierChange);
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<div
|
|
315
|
+
className="navi_table_sticky_frontier"
|
|
316
|
+
data-left=""
|
|
317
|
+
inert={!canMoveFrontier}
|
|
318
|
+
onPointerDown={(e) => {
|
|
319
|
+
if (e.button !== 0) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
e.preventDefault(); // prevent text selection
|
|
323
|
+
e.stopPropagation(); // prevent drag column
|
|
324
|
+
|
|
325
|
+
const table = tableRef.current;
|
|
326
|
+
const stickyLeftFrontierGhost = stickyLeftFrontierGhostRef.current;
|
|
327
|
+
const stickyLeftFrontierPreview = stickyLeftFrontierPreviewRef.current;
|
|
328
|
+
const colgroup = table.querySelector("colgroup");
|
|
329
|
+
const colElements = Array.from(colgroup.children);
|
|
330
|
+
initMoveStickyFrontierViaPointer(e, {
|
|
331
|
+
table,
|
|
332
|
+
frontierGhost: stickyLeftFrontierGhost,
|
|
333
|
+
frontierPreview: stickyLeftFrontierPreview,
|
|
334
|
+
elements: colElements,
|
|
335
|
+
frontierIndex: stickyLeftFrontierColumnIndex,
|
|
336
|
+
axis: "x",
|
|
337
|
+
onRelease: (_, index) => {
|
|
338
|
+
if (index !== stickyLeftFrontierColumnIndex) {
|
|
339
|
+
onStickyLeftFrontierChange(index);
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
}}
|
|
344
|
+
></div>
|
|
345
|
+
);
|
|
346
|
+
};
|
|
347
|
+
const TableStickyTopFrontier = ({
|
|
348
|
+
tableRef,
|
|
349
|
+
stickyTopFrontierGhostRef,
|
|
350
|
+
stickyTopFrontierPreviewRef,
|
|
351
|
+
}) => {
|
|
352
|
+
const { stickyTopFrontierRowIndex, onStickyTopFrontierChange } =
|
|
353
|
+
useContext(TableStickyContext);
|
|
354
|
+
const canMoveFrontier = Boolean(onStickyTopFrontierChange);
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<div
|
|
358
|
+
className="navi_table_sticky_frontier"
|
|
359
|
+
data-top=""
|
|
360
|
+
inert={!canMoveFrontier}
|
|
361
|
+
onPointerDown={(e) => {
|
|
362
|
+
if (e.button !== 0) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
e.preventDefault(); // prevent text selection
|
|
366
|
+
e.stopPropagation(); // prevent drag column
|
|
367
|
+
|
|
368
|
+
const table = tableRef.current;
|
|
369
|
+
const rowElements = Array.from(table.querySelectorAll("tr"));
|
|
370
|
+
initMoveStickyFrontierViaPointer(e, {
|
|
371
|
+
table,
|
|
372
|
+
frontierGhost: stickyTopFrontierGhostRef.current,
|
|
373
|
+
frontierPreview: stickyTopFrontierPreviewRef.current,
|
|
374
|
+
elements: rowElements,
|
|
375
|
+
frontierIndex: stickyTopFrontierRowIndex,
|
|
376
|
+
axis: "y",
|
|
377
|
+
onRelease: (_, index) => {
|
|
378
|
+
if (index !== stickyTopFrontierRowIndex) {
|
|
379
|
+
onStickyTopFrontierChange(index);
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
}}
|
|
384
|
+
></div>
|
|
385
|
+
);
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// Generic function to handle sticky frontier movement for both axes
|
|
389
|
+
const initMoveStickyFrontierViaPointer = (
|
|
390
|
+
pointerdownEvent,
|
|
391
|
+
{
|
|
392
|
+
table,
|
|
393
|
+
frontierGhost,
|
|
394
|
+
frontierPreview,
|
|
395
|
+
frontierIndex,
|
|
396
|
+
onGrab,
|
|
397
|
+
onDrag,
|
|
398
|
+
onRelease,
|
|
399
|
+
// Axis-specific configuration
|
|
400
|
+
axis, // 'x' or 'y'
|
|
401
|
+
elements, // array of colElements or rowElements
|
|
402
|
+
},
|
|
403
|
+
) => {
|
|
404
|
+
// Get elements based on axis
|
|
405
|
+
const gestureName =
|
|
406
|
+
axis === "x" ? "move-sticky-left-frontier" : "move-sticky-top-frontier";
|
|
407
|
+
const scrollProperty = axis === "x" ? "scrollLeft" : "scrollTop";
|
|
408
|
+
const ghostVariableName = "--sticky-frontier-ghost-position";
|
|
409
|
+
const previewVariableName = "--sticky-frontier-preview-position";
|
|
410
|
+
const ghostElement = frontierGhost;
|
|
411
|
+
const previewElement = frontierPreview;
|
|
412
|
+
const scrollContainer = getScrollContainer(table);
|
|
413
|
+
// Reset scroll to prevent starting drag in obstacle position
|
|
414
|
+
scrollContainer[scrollProperty] = 0;
|
|
415
|
+
|
|
416
|
+
// Setup table dimensions for ghost/preview
|
|
417
|
+
const ghostOffsetParent = ghostElement.offsetParent;
|
|
418
|
+
const ghostOffsetParentRect = ghostOffsetParent.getBoundingClientRect();
|
|
419
|
+
|
|
420
|
+
const getPosition = (elementRect) => {
|
|
421
|
+
if (axis === "x") {
|
|
422
|
+
const elementLeftRelative = elementRect.left - ghostOffsetParentRect.left;
|
|
423
|
+
return elementLeftRelative + elementRect.width;
|
|
424
|
+
}
|
|
425
|
+
const elementTopRelative = elementRect.top - ghostOffsetParentRect.top;
|
|
426
|
+
return elementTopRelative + elementRect.height;
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
// Setup initial ghost position
|
|
430
|
+
if (frontierIndex === -1) {
|
|
431
|
+
ghostElement.style.setProperty(ghostVariableName, "0px");
|
|
432
|
+
} else {
|
|
433
|
+
const element = elements[frontierIndex];
|
|
434
|
+
const elementRect = element.getBoundingClientRect();
|
|
435
|
+
const position = getPosition(elementRect);
|
|
436
|
+
ghostElement.style.setProperty(ghostVariableName, `${position}px`);
|
|
437
|
+
}
|
|
438
|
+
ghostElement.setAttribute("data-visible", "");
|
|
439
|
+
|
|
440
|
+
let futureFrontierIndex = frontierIndex;
|
|
441
|
+
const onFutureFrontierIndexChange = (index) => {
|
|
442
|
+
futureFrontierIndex = index;
|
|
443
|
+
|
|
444
|
+
// We maintain a visible preview of the frontier
|
|
445
|
+
// to tell user "hey if you grab here, the frontier will be there"
|
|
446
|
+
// At this stage user can see 3 frontiers. Where it is, the one he grab, the future one if he releases.
|
|
447
|
+
if (index === frontierIndex) {
|
|
448
|
+
previewElement.removeAttribute("data-visible");
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
let previewPosition;
|
|
452
|
+
if (index === -1) {
|
|
453
|
+
previewPosition = 0;
|
|
454
|
+
} else {
|
|
455
|
+
const element = elements[index];
|
|
456
|
+
const elementRect = element.getBoundingClientRect();
|
|
457
|
+
previewPosition = getPosition(elementRect);
|
|
458
|
+
}
|
|
459
|
+
previewElement.style.setProperty(
|
|
460
|
+
previewVariableName,
|
|
461
|
+
`${previewPosition}px`,
|
|
462
|
+
);
|
|
463
|
+
previewElement.setAttribute("data-visible", "");
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
const moveFrontierGestureController = createDragToMoveGestureController({
|
|
467
|
+
name: gestureName,
|
|
468
|
+
direction: { [axis]: true },
|
|
469
|
+
backdropZIndex: Z_INDEX_STICKY_FRONTIER_BACKDROP,
|
|
470
|
+
areaConstraint: "visible",
|
|
471
|
+
areaConstraintElement: table.closest(".navi_table_root"),
|
|
472
|
+
|
|
473
|
+
onGrab,
|
|
474
|
+
onDrag: (gestureInfo) => {
|
|
475
|
+
const dropTargetInfo = getDropTargetInfo(gestureInfo, elements);
|
|
476
|
+
if (dropTargetInfo) {
|
|
477
|
+
const dropColumnIndex = dropTargetInfo.index;
|
|
478
|
+
const dropFrontierIndex =
|
|
479
|
+
dropTargetInfo.elementSide[axis] === "start"
|
|
480
|
+
? dropColumnIndex - 1
|
|
481
|
+
: dropColumnIndex;
|
|
482
|
+
if (dropFrontierIndex !== futureFrontierIndex) {
|
|
483
|
+
onFutureFrontierIndexChange(dropFrontierIndex);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
onDrag?.(gestureInfo, futureFrontierIndex);
|
|
487
|
+
},
|
|
488
|
+
onRelease: (gesture) => {
|
|
489
|
+
previewElement.removeAttribute("data-visible");
|
|
490
|
+
previewElement.style.removeProperty(previewVariableName);
|
|
491
|
+
ghostElement.removeAttribute("data-visible");
|
|
492
|
+
ghostElement.style.removeProperty(ghostVariableName);
|
|
493
|
+
ghostElement.style[axis === "x" ? "left" : "top"] = ""; // reset position set by drag
|
|
494
|
+
|
|
495
|
+
onRelease?.(gesture, futureFrontierIndex);
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
moveFrontierGestureController.grabViaPointer(pointerdownEvent, {
|
|
499
|
+
element: ghostElement,
|
|
500
|
+
});
|
|
501
|
+
};
|