@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,584 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Table Fixed Headers Demo - Large Dataset</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family:
|
|
10
|
+
system-ui,
|
|
11
|
+
-apple-system,
|
|
12
|
+
sans-serif;
|
|
13
|
+
margin: 0px;
|
|
14
|
+
padding: 0;
|
|
15
|
+
background: #f5f5f5;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.demo-container {
|
|
19
|
+
max-width: 1000px;
|
|
20
|
+
margin: 0 auto;
|
|
21
|
+
background: white;
|
|
22
|
+
padding: 100px;
|
|
23
|
+
border-radius: 8px;
|
|
24
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Status display */
|
|
28
|
+
.status {
|
|
29
|
+
margin-top: 20px;
|
|
30
|
+
padding: 10px;
|
|
31
|
+
background: #f0f8ff;
|
|
32
|
+
border-radius: 4px;
|
|
33
|
+
font-family: monospace;
|
|
34
|
+
font-size: 14px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.actions {
|
|
38
|
+
margin-top: 15px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
button {
|
|
42
|
+
background: #0078d4;
|
|
43
|
+
color: white;
|
|
44
|
+
border: none;
|
|
45
|
+
padding: 8px 16px;
|
|
46
|
+
border-radius: 4px;
|
|
47
|
+
cursor: pointer;
|
|
48
|
+
margin-right: 8px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
button:hover {
|
|
52
|
+
background: #106ebe;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
button:disabled {
|
|
56
|
+
background: #ccc;
|
|
57
|
+
cursor: not-allowed;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.info {
|
|
61
|
+
background: #fff3cd;
|
|
62
|
+
border: 1px solid #ffeaa7;
|
|
63
|
+
border-radius: 4px;
|
|
64
|
+
padding: 15px;
|
|
65
|
+
margin: 15px 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.info h3 {
|
|
69
|
+
margin-top: 0;
|
|
70
|
+
color: #856404;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.info p {
|
|
74
|
+
margin-bottom: 0;
|
|
75
|
+
color: #856404;
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
78
|
+
</head>
|
|
79
|
+
<body>
|
|
80
|
+
<div id="root"></div>
|
|
81
|
+
|
|
82
|
+
<script type="module" jsenv-type="module/jsx">
|
|
83
|
+
import { render } from "preact";
|
|
84
|
+
import { useRef, useState } from "preact/hooks";
|
|
85
|
+
import {
|
|
86
|
+
// eslint-disable-next-line no-unused-vars
|
|
87
|
+
Table,
|
|
88
|
+
// eslint-disable-next-line no-unused-vars
|
|
89
|
+
ActiveKeyboardShortcuts,
|
|
90
|
+
// eslint-disable-next-line no-unused-vars
|
|
91
|
+
TableCell,
|
|
92
|
+
// eslint-disable-next-line no-unused-vars
|
|
93
|
+
Colgroup,
|
|
94
|
+
// eslint-disable-next-line no-unused-vars
|
|
95
|
+
Col,
|
|
96
|
+
// eslint-disable-next-line no-unused-vars
|
|
97
|
+
Thead,
|
|
98
|
+
// eslint-disable-next-line no-unused-vars
|
|
99
|
+
Tbody,
|
|
100
|
+
// eslint-disable-next-line no-unused-vars
|
|
101
|
+
Tr,
|
|
102
|
+
// eslint-disable-next-line no-unused-vars
|
|
103
|
+
RowNumberCol,
|
|
104
|
+
// eslint-disable-next-line no-unused-vars
|
|
105
|
+
RowNumberTableCell,
|
|
106
|
+
useCellsAndColumns,
|
|
107
|
+
stringifyTableSelectionValue,
|
|
108
|
+
} from "@jsenv/navi";
|
|
109
|
+
|
|
110
|
+
// Generate large dataset to force scrolling
|
|
111
|
+
const generateLargeDataset = (rows = 50, cols = 10) => {
|
|
112
|
+
const data = [];
|
|
113
|
+
for (let i = 1; i <= rows; i++) {
|
|
114
|
+
const rowCells = [];
|
|
115
|
+
rowCells.id = i;
|
|
116
|
+
for (let j = 1; j <= cols; j++) {
|
|
117
|
+
if (i === 1 && j === 1) {
|
|
118
|
+
// rowCells.push(`Cell ${i}-${j}`);
|
|
119
|
+
rowCells.push(`A text that is longer`);
|
|
120
|
+
} else {
|
|
121
|
+
rowCells.push(`Cell ${i}-${j}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
data.push(rowCells);
|
|
125
|
+
}
|
|
126
|
+
return data;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Create large dataset (50 rows x 10 columns = 500 cells)
|
|
130
|
+
const data = generateLargeDataset(50, 10);
|
|
131
|
+
const columnsConfig = Array.from({ length: 10 }, (_, i) => ({
|
|
132
|
+
id: `col${i + 1}`,
|
|
133
|
+
header: i === 7 ? "A larger header" : `Column ${i + 1}`,
|
|
134
|
+
width: i === 0 ? 50 : undefined,
|
|
135
|
+
}));
|
|
136
|
+
|
|
137
|
+
// eslint-disable-next-line no-unused-vars
|
|
138
|
+
const FixedHeadersDemo = () => {
|
|
139
|
+
const tableRef = useRef(null);
|
|
140
|
+
const [selection, setSelection] = useState([]);
|
|
141
|
+
const [columnWidths, setColumnWidths] = useState([]);
|
|
142
|
+
const [rowHeights, setRowHeights] = useState([]);
|
|
143
|
+
const setColumnValue = (columnId, value) => {
|
|
144
|
+
console.log(`Set column ${columnId} value to "${value}"`);
|
|
145
|
+
};
|
|
146
|
+
const { cells, setCellValue, columns, setColumnOrder } =
|
|
147
|
+
useCellsAndColumns(data, columnsConfig);
|
|
148
|
+
const [
|
|
149
|
+
stickyLeftFrontierColumnIndex,
|
|
150
|
+
setStickyLeftFrontierColumnIndex,
|
|
151
|
+
] = useState(() => {
|
|
152
|
+
const saved = localStorage.getItem(
|
|
153
|
+
"table-demo-stickyLeftFrontierColumnIndex",
|
|
154
|
+
);
|
|
155
|
+
return saved ? parseInt(saved, 10) : 2;
|
|
156
|
+
});
|
|
157
|
+
const [stickyTopFrontierRowIndex, setStickyTopFrontierRowIndex] =
|
|
158
|
+
useState(() => {
|
|
159
|
+
const saved = localStorage.getItem(
|
|
160
|
+
"table-demo-stickyTopFrontierRowIndex",
|
|
161
|
+
);
|
|
162
|
+
return saved ? parseInt(saved, 10) : 2;
|
|
163
|
+
});
|
|
164
|
+
const [useContainerScrolling, setUseContainerScrolling] = useState(
|
|
165
|
+
() => {
|
|
166
|
+
// Load from localStorage, default to false if not found
|
|
167
|
+
const saved = localStorage.getItem(
|
|
168
|
+
"table-demo-useContainerScrolling",
|
|
169
|
+
);
|
|
170
|
+
return saved === "true";
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<div className="demo-container">
|
|
176
|
+
<h1>Table Fixed Headers Demo - Large Dataset</h1>
|
|
177
|
+
|
|
178
|
+
<details>
|
|
179
|
+
<summary>Instructions for Testing</summary>
|
|
180
|
+
|
|
181
|
+
<div className="info">
|
|
182
|
+
<h3>Testing Fixed Row/Column Headers with Cell Editing</h3>
|
|
183
|
+
<p>
|
|
184
|
+
This demo contains a large dataset (50 rows × 10 columns = 500
|
|
185
|
+
cells) in a constrained container to force scrolling. The goal
|
|
186
|
+
is to test and implement fixed first row (header) and first
|
|
187
|
+
column behavior, plus cell editing functionality.
|
|
188
|
+
</p>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<p>
|
|
192
|
+
<strong>Current behavior:</strong> The table will scroll
|
|
193
|
+
normally with editable cells.
|
|
194
|
+
<br />
|
|
195
|
+
<strong>Cell editing:</strong> Double-click any data cell to
|
|
196
|
+
edit its value. Press Enter to save or Escape to cancel.
|
|
197
|
+
<br />
|
|
198
|
+
<strong>Desired behavior:</strong> First row and first column
|
|
199
|
+
should remain fixed while scrolling.
|
|
200
|
+
</p>
|
|
201
|
+
</details>
|
|
202
|
+
|
|
203
|
+
<div>
|
|
204
|
+
<button
|
|
205
|
+
onClick={() => {
|
|
206
|
+
const newValue = !useContainerScrolling;
|
|
207
|
+
setUseContainerScrolling(newValue);
|
|
208
|
+
// Save to localStorage for persistence
|
|
209
|
+
localStorage.setItem(
|
|
210
|
+
"table-demo-useContainerScrolling",
|
|
211
|
+
newValue.toString(),
|
|
212
|
+
);
|
|
213
|
+
}}
|
|
214
|
+
style={{
|
|
215
|
+
background: useContainerScrolling ? "#28a745" : "#6c757d",
|
|
216
|
+
fontWeight: "bold",
|
|
217
|
+
}}
|
|
218
|
+
>
|
|
219
|
+
Scrolling:{" "}
|
|
220
|
+
{useContainerScrolling ? "Container (400×600px)" : "Document"}
|
|
221
|
+
</button>
|
|
222
|
+
|
|
223
|
+
<div style={{ marginTop: "15px" }}>
|
|
224
|
+
<label style={{ marginRight: "15px" }}>
|
|
225
|
+
Sticky Left Frontier (Column Index):{" "}
|
|
226
|
+
<input
|
|
227
|
+
type="number"
|
|
228
|
+
min="-1"
|
|
229
|
+
max="10"
|
|
230
|
+
value={stickyLeftFrontierColumnIndex}
|
|
231
|
+
onChange={(e) => {
|
|
232
|
+
const newValue = parseInt(e.target.value, 10);
|
|
233
|
+
setStickyLeftFrontierColumnIndex(newValue);
|
|
234
|
+
localStorage.setItem(
|
|
235
|
+
"table-demo-stickyLeftFrontierColumnIndex",
|
|
236
|
+
newValue.toString(),
|
|
237
|
+
);
|
|
238
|
+
}}
|
|
239
|
+
style={{
|
|
240
|
+
width: "60px",
|
|
241
|
+
marginLeft: "5px",
|
|
242
|
+
padding: "2px 6px",
|
|
243
|
+
border: "1px solid #ccc",
|
|
244
|
+
borderRadius: "3px",
|
|
245
|
+
}}
|
|
246
|
+
/>
|
|
247
|
+
</label>
|
|
248
|
+
|
|
249
|
+
<label>
|
|
250
|
+
Sticky Top Frontier (Row Index):{" "}
|
|
251
|
+
<input
|
|
252
|
+
type="number"
|
|
253
|
+
min="-1"
|
|
254
|
+
max="50"
|
|
255
|
+
value={stickyTopFrontierRowIndex}
|
|
256
|
+
onChange={(e) => {
|
|
257
|
+
const newValue = parseInt(e.target.value, 10);
|
|
258
|
+
setStickyTopFrontierRowIndex(newValue);
|
|
259
|
+
localStorage.setItem(
|
|
260
|
+
"table-demo-stickyTopFrontierRowIndex",
|
|
261
|
+
newValue.toString(),
|
|
262
|
+
);
|
|
263
|
+
}}
|
|
264
|
+
style={{
|
|
265
|
+
width: "60px",
|
|
266
|
+
marginLeft: "5px",
|
|
267
|
+
padding: "2px 6px",
|
|
268
|
+
border: "1px solid #ccc",
|
|
269
|
+
borderRadius: "3px",
|
|
270
|
+
}}
|
|
271
|
+
/>
|
|
272
|
+
</label>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
<br />
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<div className="table-container">
|
|
279
|
+
<Table
|
|
280
|
+
ref={tableRef}
|
|
281
|
+
selection={selection}
|
|
282
|
+
onSelectionChange={(value) => {
|
|
283
|
+
setSelection(value);
|
|
284
|
+
}}
|
|
285
|
+
onColumnSizeChange={(width, columnIndex, column) => {
|
|
286
|
+
console.log(column.id, `resized to ${width}px`);
|
|
287
|
+
setColumnWidths((columnWidths) => {
|
|
288
|
+
columnWidths[columnIndex] = width;
|
|
289
|
+
return [...columnWidths];
|
|
290
|
+
});
|
|
291
|
+
}}
|
|
292
|
+
onRowSizeChange={(height, rowIndex, row) => {
|
|
293
|
+
console.log(row.id, `resized to ${height}px`, rowIndex);
|
|
294
|
+
setRowHeights((rowHeights) => {
|
|
295
|
+
rowHeights[rowIndex] = height;
|
|
296
|
+
return [...rowHeights];
|
|
297
|
+
});
|
|
298
|
+
}}
|
|
299
|
+
stickyLeftFrontierColumnIndex={stickyLeftFrontierColumnIndex}
|
|
300
|
+
onStickyLeftFrontierChange={setStickyLeftFrontierColumnIndex}
|
|
301
|
+
stickyTopFrontierRowIndex={stickyTopFrontierRowIndex}
|
|
302
|
+
onStickyTopFrontierChange={setStickyTopFrontierRowIndex}
|
|
303
|
+
onColumnOrderChange={(newColumnOrder) => {
|
|
304
|
+
console.log("Column order changed:", newColumnOrder);
|
|
305
|
+
setColumnOrder(newColumnOrder);
|
|
306
|
+
}}
|
|
307
|
+
borderCollapse
|
|
308
|
+
overflow={useContainerScrolling ? "auto" : undefined}
|
|
309
|
+
maxHeight={useContainerScrolling ? 400 : undefined}
|
|
310
|
+
maxWidth={useContainerScrolling ? 600 : undefined}
|
|
311
|
+
>
|
|
312
|
+
<Colgroup>
|
|
313
|
+
<RowNumberCol width={columnWidths[0]} immovable={false} />
|
|
314
|
+
{columns.map((column, columnIndex) => (
|
|
315
|
+
<Col
|
|
316
|
+
key={column.id}
|
|
317
|
+
id={column.id}
|
|
318
|
+
width={columnWidths[columnIndex + 1] || column.width}
|
|
319
|
+
/>
|
|
320
|
+
))}
|
|
321
|
+
</Colgroup>
|
|
322
|
+
<Thead>
|
|
323
|
+
<Tr id="head" height={rowHeights[0]}>
|
|
324
|
+
<RowNumberTableCell
|
|
325
|
+
// just to help debugging column drag
|
|
326
|
+
// in practice it won't work as it's purely visual
|
|
327
|
+
// and column ordering works only for columns controlled by a state
|
|
328
|
+
canDragColumn
|
|
329
|
+
/>
|
|
330
|
+
{columns.map((col) => (
|
|
331
|
+
<TableCell
|
|
332
|
+
key={col.id}
|
|
333
|
+
action={(value) => {
|
|
334
|
+
setColumnValue(col.id, value);
|
|
335
|
+
}}
|
|
336
|
+
>
|
|
337
|
+
{col.header}
|
|
338
|
+
</TableCell>
|
|
339
|
+
))}
|
|
340
|
+
</Tr>
|
|
341
|
+
</Thead>
|
|
342
|
+
<Tbody>
|
|
343
|
+
{cells.map((rowCells, dataRowIndex) => {
|
|
344
|
+
const rowIndex = dataRowIndex + 1;
|
|
345
|
+
const rowData = data[dataRowIndex];
|
|
346
|
+
return (
|
|
347
|
+
<Tr
|
|
348
|
+
id={rowData.id}
|
|
349
|
+
key={rowData.id}
|
|
350
|
+
height={rowHeights[rowIndex]}
|
|
351
|
+
>
|
|
352
|
+
<RowNumberTableCell />
|
|
353
|
+
{rowCells.map((cellValue, columnIndex) => {
|
|
354
|
+
return (
|
|
355
|
+
<TableCell
|
|
356
|
+
key={columns[columnIndex].id}
|
|
357
|
+
action={(value) => {
|
|
358
|
+
setCellValue(
|
|
359
|
+
{ rowIndex: dataRowIndex, columnIndex },
|
|
360
|
+
value,
|
|
361
|
+
);
|
|
362
|
+
}}
|
|
363
|
+
>
|
|
364
|
+
{cellValue}
|
|
365
|
+
</TableCell>
|
|
366
|
+
);
|
|
367
|
+
})}
|
|
368
|
+
</Tr>
|
|
369
|
+
);
|
|
370
|
+
})}
|
|
371
|
+
</Tbody>
|
|
372
|
+
</Table>
|
|
373
|
+
</div>
|
|
374
|
+
|
|
375
|
+
<div className="status">
|
|
376
|
+
<div>
|
|
377
|
+
Dataset size:{" "}
|
|
378
|
+
<span>
|
|
379
|
+
{cells.length} rows × {cells[0]?.length || 0} columns ={" "}
|
|
380
|
+
{cells.length * (cells[0]?.length || 0)} cells
|
|
381
|
+
</span>
|
|
382
|
+
</div>
|
|
383
|
+
<div>
|
|
384
|
+
Selection:{" "}
|
|
385
|
+
<span>
|
|
386
|
+
{JSON.stringify(selection.slice(0, 5))}
|
|
387
|
+
{selection.length > 5
|
|
388
|
+
? `... (+${selection.length - 5} more)`
|
|
389
|
+
: ""}
|
|
390
|
+
</span>
|
|
391
|
+
</div>
|
|
392
|
+
<div>
|
|
393
|
+
Total selection count: <span>{selection.length}</span>
|
|
394
|
+
</div>
|
|
395
|
+
<div>
|
|
396
|
+
Editable cells:{" "}
|
|
397
|
+
<span>
|
|
398
|
+
{cells.length * (cells[0]?.length || 0)} cells with editing
|
|
399
|
+
enabled
|
|
400
|
+
</span>
|
|
401
|
+
</div>
|
|
402
|
+
<div>
|
|
403
|
+
Column order:{" "}
|
|
404
|
+
<span>{JSON.stringify(columns.map((c) => c.id))}</span>
|
|
405
|
+
</div>
|
|
406
|
+
<div>
|
|
407
|
+
Column widths:{" "}
|
|
408
|
+
<span>
|
|
409
|
+
{columnWidths.length
|
|
410
|
+
? JSON.stringify(columnWidths)
|
|
411
|
+
: "Default (auto)"}
|
|
412
|
+
</span>
|
|
413
|
+
</div>
|
|
414
|
+
<div>
|
|
415
|
+
Row heights:{" "}
|
|
416
|
+
<span>
|
|
417
|
+
{rowHeights.length
|
|
418
|
+
? JSON.stringify(rowHeights)
|
|
419
|
+
: "Default (auto)"}
|
|
420
|
+
</span>
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
|
|
424
|
+
<div className="actions">
|
|
425
|
+
<button
|
|
426
|
+
onClick={() => {
|
|
427
|
+
setSelection([]);
|
|
428
|
+
}}
|
|
429
|
+
>
|
|
430
|
+
Clear Selection
|
|
431
|
+
</button>
|
|
432
|
+
<button
|
|
433
|
+
onClick={() => {
|
|
434
|
+
const allCells = [];
|
|
435
|
+
for (let y = 0; y < cells.length; y++) {
|
|
436
|
+
for (let x = 0; x < (cells[0]?.length || 0); x++) {
|
|
437
|
+
allCells.push(
|
|
438
|
+
stringifyTableSelectionValue("cell", {
|
|
439
|
+
rowIndex: y + 1,
|
|
440
|
+
columnIndex: x + 1,
|
|
441
|
+
}),
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
setSelection(allCells);
|
|
446
|
+
}}
|
|
447
|
+
>
|
|
448
|
+
Select All ({cells.length * (cells[0]?.length || 0)} cells)
|
|
449
|
+
</button>
|
|
450
|
+
<button
|
|
451
|
+
onClick={() => {
|
|
452
|
+
const firstRowCells = columns.map((_, columnIndex) =>
|
|
453
|
+
stringifyTableSelectionValue("cell", {
|
|
454
|
+
rowIndex: 0,
|
|
455
|
+
columnIndex: columnIndex + 1,
|
|
456
|
+
}),
|
|
457
|
+
);
|
|
458
|
+
setSelection(firstRowCells);
|
|
459
|
+
}}
|
|
460
|
+
>
|
|
461
|
+
Select First Row
|
|
462
|
+
</button>
|
|
463
|
+
<button
|
|
464
|
+
onClick={() => {
|
|
465
|
+
// Select first column
|
|
466
|
+
const firstColCells = data.map((_, rowIndex) =>
|
|
467
|
+
stringifyTableSelectionValue("cell", {
|
|
468
|
+
rowIndex,
|
|
469
|
+
columnIndex: 0,
|
|
470
|
+
}),
|
|
471
|
+
);
|
|
472
|
+
setSelection(firstColCells);
|
|
473
|
+
}}
|
|
474
|
+
>
|
|
475
|
+
Select First Column
|
|
476
|
+
</button>
|
|
477
|
+
<button
|
|
478
|
+
onClick={() => {
|
|
479
|
+
setColumnWidths([]);
|
|
480
|
+
}}
|
|
481
|
+
>
|
|
482
|
+
Reset Column Widths
|
|
483
|
+
</button>
|
|
484
|
+
<button
|
|
485
|
+
onClick={() => {
|
|
486
|
+
setRowHeights([]);
|
|
487
|
+
}}
|
|
488
|
+
>
|
|
489
|
+
Reset Row Heights
|
|
490
|
+
</button>
|
|
491
|
+
<button
|
|
492
|
+
onClick={() => {
|
|
493
|
+
// Set a sample cell to demonstrate editing works
|
|
494
|
+
// setBaseCellValues((prevValues) => {
|
|
495
|
+
// const newValues = prevValues.map((row, rowIdx) =>
|
|
496
|
+
// rowIdx === 0
|
|
497
|
+
// ? row.map((cell, colIdx) =>
|
|
498
|
+
// colIdx === 0
|
|
499
|
+
// ? `Edited at ${new Date().toLocaleTimeString()}`
|
|
500
|
+
// : cell,
|
|
501
|
+
// )
|
|
502
|
+
// : [...row],
|
|
503
|
+
// );
|
|
504
|
+
// return newValues;
|
|
505
|
+
// });
|
|
506
|
+
}}
|
|
507
|
+
>
|
|
508
|
+
Demo Edit Cell (1,1)
|
|
509
|
+
</button>
|
|
510
|
+
<button
|
|
511
|
+
onClick={() => {
|
|
512
|
+
setColumnOrder([...columns.map((c) => c.id)].reverse());
|
|
513
|
+
}}
|
|
514
|
+
>
|
|
515
|
+
Reverse Column Order
|
|
516
|
+
</button>
|
|
517
|
+
<button
|
|
518
|
+
onClick={() => {
|
|
519
|
+
const shuffled = [...columns.map((c) => c.id)];
|
|
520
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
521
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
522
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
523
|
+
}
|
|
524
|
+
setColumnOrder(shuffled);
|
|
525
|
+
}}
|
|
526
|
+
>
|
|
527
|
+
Shuffle Columns
|
|
528
|
+
</button>
|
|
529
|
+
<button
|
|
530
|
+
onClick={() => {
|
|
531
|
+
setColumnOrder(columnsConfig.map((col) => col.id));
|
|
532
|
+
}}
|
|
533
|
+
>
|
|
534
|
+
Reset Column Order
|
|
535
|
+
</button>
|
|
536
|
+
</div>
|
|
537
|
+
|
|
538
|
+
{/*
|
|
539
|
+
<div>
|
|
540
|
+
<h3>Active keyboard shortcuts</h3>
|
|
541
|
+
<ActiveKeyboardShortcuts visible />
|
|
542
|
+
</div>
|
|
543
|
+
|
|
544
|
+
<div className="info">
|
|
545
|
+
<h3>Instructions for Testing</h3>
|
|
546
|
+
<p>
|
|
547
|
+
1. Scroll the table horizontally and vertically
|
|
548
|
+
<br />
|
|
549
|
+
2. Observe how headers behave during scrolling
|
|
550
|
+
<br />
|
|
551
|
+
3. Test selection behavior while scrolled
|
|
552
|
+
<br />
|
|
553
|
+
4. <strong>Double-click any data cell to edit its value</strong>
|
|
554
|
+
<br />
|
|
555
|
+
5. Use Enter to save changes, Escape to cancel editing
|
|
556
|
+
<br />
|
|
557
|
+
6. Check if borders and selection overlay work correctly with
|
|
558
|
+
fixed headers
|
|
559
|
+
</p>
|
|
560
|
+
</div>
|
|
561
|
+
*/}
|
|
562
|
+
</div>
|
|
563
|
+
);
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
// Render the app
|
|
567
|
+
render(<FixedHeadersDemo />, document.getElementById("root"));
|
|
568
|
+
|
|
569
|
+
setTimeout(() => {
|
|
570
|
+
// document.querySelector(".navi_table_root").scrollLeft = 1500;
|
|
571
|
+
}, 500);
|
|
572
|
+
|
|
573
|
+
console.log("Fixed Headers Demo loaded with large dataset:");
|
|
574
|
+
console.log(
|
|
575
|
+
`- ${data.length} rows × 10 columns = ${data.length * 10} total cells`,
|
|
576
|
+
);
|
|
577
|
+
console.log("- Double-click data cells to edit values");
|
|
578
|
+
console.log("- Scroll the table to test fixed header behavior");
|
|
579
|
+
console.log(
|
|
580
|
+
"- Use selection features to test interaction with scrolling",
|
|
581
|
+
);
|
|
582
|
+
</script>
|
|
583
|
+
</body>
|
|
584
|
+
</html>
|