@jsenv/navi 0.0.1 → 0.1.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.
- package/dist/jsenv_navi.js +22954 -0
- package/index.js +66 -16
- package/package.json +22 -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/{input → field}/input_textual.jsx +247 -173
- 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/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,148 @@
|
|
|
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>Isolated Item Tracker Demo</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: Arial, sans-serif;
|
|
10
|
+
margin: 10px;
|
|
11
|
+
background: #f5f5f5;
|
|
12
|
+
font-size: 12px;
|
|
13
|
+
}
|
|
14
|
+
.debug-panel {
|
|
15
|
+
background: white;
|
|
16
|
+
padding: 10px;
|
|
17
|
+
margin: 5px 0;
|
|
18
|
+
border-radius: 4px;
|
|
19
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
20
|
+
}
|
|
21
|
+
.item-data {
|
|
22
|
+
background: #e3f2fd;
|
|
23
|
+
padding: 8px;
|
|
24
|
+
margin: 4px 0;
|
|
25
|
+
border-radius: 3px;
|
|
26
|
+
border-left: 3px solid #2196f3;
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
justify-content: space-between;
|
|
30
|
+
font-size: 11px;
|
|
31
|
+
}
|
|
32
|
+
.item-info {
|
|
33
|
+
flex: 1;
|
|
34
|
+
}
|
|
35
|
+
.item-controls {
|
|
36
|
+
display: flex;
|
|
37
|
+
gap: 4px;
|
|
38
|
+
align-items: center;
|
|
39
|
+
}
|
|
40
|
+
.item-consumer {
|
|
41
|
+
background: #f3e5f5;
|
|
42
|
+
padding: 6px;
|
|
43
|
+
margin: 3px 0;
|
|
44
|
+
border-radius: 3px;
|
|
45
|
+
border-left: 3px solid #9c27b0;
|
|
46
|
+
display: flex;
|
|
47
|
+
justify-content: space-between;
|
|
48
|
+
align-items: center;
|
|
49
|
+
font-size: 11px;
|
|
50
|
+
}
|
|
51
|
+
button {
|
|
52
|
+
background: #4caf50;
|
|
53
|
+
color: white;
|
|
54
|
+
border: none;
|
|
55
|
+
padding: 4px 8px;
|
|
56
|
+
border-radius: 3px;
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
margin: 1px;
|
|
59
|
+
font-size: 10px;
|
|
60
|
+
}
|
|
61
|
+
button:hover {
|
|
62
|
+
background: #45a049;
|
|
63
|
+
}
|
|
64
|
+
button.remove {
|
|
65
|
+
background: #f44336;
|
|
66
|
+
}
|
|
67
|
+
button.remove:hover {
|
|
68
|
+
background: #d32f2f;
|
|
69
|
+
}
|
|
70
|
+
button.add {
|
|
71
|
+
background: #2196f3;
|
|
72
|
+
}
|
|
73
|
+
button.add:hover {
|
|
74
|
+
background: #1976d2;
|
|
75
|
+
}
|
|
76
|
+
button.rerender {
|
|
77
|
+
background: #ff9800;
|
|
78
|
+
}
|
|
79
|
+
button.rerender:hover {
|
|
80
|
+
background: #f57c00;
|
|
81
|
+
}
|
|
82
|
+
select {
|
|
83
|
+
padding: 2px 4px;
|
|
84
|
+
border: 1px solid #ddd;
|
|
85
|
+
border-radius: 3px;
|
|
86
|
+
margin: 0 4px;
|
|
87
|
+
font-size: 10px;
|
|
88
|
+
}
|
|
89
|
+
.log {
|
|
90
|
+
background: #1e1e1e;
|
|
91
|
+
color: #00ff00;
|
|
92
|
+
padding: 8px;
|
|
93
|
+
border-radius: 3px;
|
|
94
|
+
font-family: monospace;
|
|
95
|
+
max-height: 150px;
|
|
96
|
+
overflow-y: auto;
|
|
97
|
+
white-space: pre-wrap;
|
|
98
|
+
font-size: 10px;
|
|
99
|
+
}
|
|
100
|
+
.section {
|
|
101
|
+
margin: 8px 0;
|
|
102
|
+
}
|
|
103
|
+
h1 {
|
|
104
|
+
font-size: 18px;
|
|
105
|
+
margin: 5px 0;
|
|
106
|
+
}
|
|
107
|
+
h2 {
|
|
108
|
+
font-size: 14px;
|
|
109
|
+
margin: 5px 0;
|
|
110
|
+
}
|
|
111
|
+
h3 {
|
|
112
|
+
font-size: 12px;
|
|
113
|
+
margin: 4px 0;
|
|
114
|
+
}
|
|
115
|
+
strong {
|
|
116
|
+
font-size: 11px;
|
|
117
|
+
}
|
|
118
|
+
small {
|
|
119
|
+
font-size: 9px;
|
|
120
|
+
}
|
|
121
|
+
</style>
|
|
122
|
+
</head>
|
|
123
|
+
<body>
|
|
124
|
+
<h1>Isolated Item Tracker Demo</h1>
|
|
125
|
+
|
|
126
|
+
<div class="debug-panel">
|
|
127
|
+
<h2>Logs</h2>
|
|
128
|
+
<div id="logs" class="log"></div>
|
|
129
|
+
<button onclick="document.getElementById('logs').textContent = ''">
|
|
130
|
+
Clear Logs
|
|
131
|
+
</button>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<div class="debug-panel">
|
|
135
|
+
<h2>App</h2>
|
|
136
|
+
<div id="app"></div>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<script type="module" jsenv-type="module/jsx">
|
|
140
|
+
import { render } from "preact";
|
|
141
|
+
// eslint-disable-next-line no-unused-vars
|
|
142
|
+
import { App } from "./use_isolated_item_tracker_demo.jsx";
|
|
143
|
+
|
|
144
|
+
// Initial render
|
|
145
|
+
render(<App />, document.getElementById("app"));
|
|
146
|
+
</script>
|
|
147
|
+
</body>
|
|
148
|
+
</html>
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import { useRef, useState } from "preact/hooks";
|
|
2
|
+
import { createIsolatedItemTracker } from "./use_isolated_item_tracker.jsx";
|
|
3
|
+
|
|
4
|
+
// Initial state for table columns
|
|
5
|
+
const initialState = {
|
|
6
|
+
renderKey: 0,
|
|
7
|
+
columns: [
|
|
8
|
+
{ id: "name", label: "Name", width: "200px", sortable: true },
|
|
9
|
+
{ id: "email", label: "Email", width: "250px", sortable: true },
|
|
10
|
+
{ id: "status", label: "Status", width: "100px", sortable: false },
|
|
11
|
+
],
|
|
12
|
+
tableData: [
|
|
13
|
+
{ name: "John Doe", email: "john@example.com", status: "Active" },
|
|
14
|
+
{ name: "Jane Smith", email: "jane@example.com", status: "Inactive" },
|
|
15
|
+
{ name: "Bob Johnson", email: "bob@example.com", status: "Pending" },
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const [useColumnTrackerProviders, useRegisterColumn, useColumn, useColumns] =
|
|
20
|
+
createIsolatedItemTracker();
|
|
21
|
+
|
|
22
|
+
export const App = () => {
|
|
23
|
+
const [state, setState] = useState(initialState);
|
|
24
|
+
|
|
25
|
+
log(`🎬 App render ${state.renderKey}`);
|
|
26
|
+
|
|
27
|
+
const handleUpdateColumn = (index, newColumn) => {
|
|
28
|
+
setState((prev) => {
|
|
29
|
+
const newColumns = [...prev.columns];
|
|
30
|
+
newColumns[index] = newColumn;
|
|
31
|
+
log(`🔄 Updated column ${index}: ${JSON.stringify(newColumn)}`);
|
|
32
|
+
return { ...prev, columns: newColumns };
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const handleRemoveColumn = (index) => {
|
|
37
|
+
setState((prev) => {
|
|
38
|
+
const newColumns = prev.columns.filter((_, i) => i !== index);
|
|
39
|
+
log(`➖ Removed column ${index}`);
|
|
40
|
+
return { ...prev, columns: newColumns };
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const handleAddColumn = () => {
|
|
45
|
+
setState((prev) => {
|
|
46
|
+
const newId = `col${prev.columns.length + 1}`;
|
|
47
|
+
const newColumn = {
|
|
48
|
+
id: newId,
|
|
49
|
+
label: `Column ${prev.columns.length + 1}`,
|
|
50
|
+
width: "150px",
|
|
51
|
+
sortable: true,
|
|
52
|
+
};
|
|
53
|
+
log(`➕ Added column: ${JSON.stringify(newColumn)}`);
|
|
54
|
+
return { ...prev, columns: [...prev.columns, newColumn] };
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handleMoveColumnUp = (index) => {
|
|
59
|
+
if (index === 0) return; // Can't move first column up
|
|
60
|
+
setState((prev) => {
|
|
61
|
+
const newColumns = [...prev.columns];
|
|
62
|
+
// Swap with previous column
|
|
63
|
+
[newColumns[index - 1], newColumns[index]] = [
|
|
64
|
+
newColumns[index],
|
|
65
|
+
newColumns[index - 1],
|
|
66
|
+
];
|
|
67
|
+
log(`⬆️ Moved column ${index} up to position ${index - 1}`);
|
|
68
|
+
return { ...prev, columns: newColumns };
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const handleMoveColumnDown = (index) => {
|
|
73
|
+
setState((prev) => {
|
|
74
|
+
if (index === prev.columns.length - 1) return prev; // Can't move last column down
|
|
75
|
+
const newColumns = [...prev.columns];
|
|
76
|
+
// Swap with next column
|
|
77
|
+
[newColumns[index], newColumns[index + 1]] = [
|
|
78
|
+
newColumns[index + 1],
|
|
79
|
+
newColumns[index],
|
|
80
|
+
];
|
|
81
|
+
log(`⬇️ Moved column ${index} down to position ${index + 1}`);
|
|
82
|
+
return { ...prev, columns: newColumns };
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const [ColumnProducerProvider, ColumnConsumerProvider] =
|
|
87
|
+
useColumnTrackerProviders();
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div>
|
|
91
|
+
<div style={{ marginBottom: "20px" }}>
|
|
92
|
+
<strong>App Render #{state.renderKey}</strong>
|
|
93
|
+
<button
|
|
94
|
+
style={{ marginLeft: "10px" }}
|
|
95
|
+
onClick={() => {
|
|
96
|
+
log("🔄 Manual app re-render triggered");
|
|
97
|
+
setState((prev) => ({
|
|
98
|
+
...prev,
|
|
99
|
+
renderKey: prev.renderKey + 1,
|
|
100
|
+
}));
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
Re-render App
|
|
104
|
+
</button>
|
|
105
|
+
<button style={{ marginLeft: "10px" }} onClick={handleAddColumn}>
|
|
106
|
+
➕ Add Column
|
|
107
|
+
</button>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<div className="section">
|
|
111
|
+
<h3>Column Configuration (affects both producer & consumer)</h3>
|
|
112
|
+
{state.columns.map((column, index) => (
|
|
113
|
+
<ColumnDataControls
|
|
114
|
+
key={`${column.id}-${state.renderKey}`}
|
|
115
|
+
column={column}
|
|
116
|
+
index={index}
|
|
117
|
+
totalColumns={state.columns.length}
|
|
118
|
+
onUpdate={handleUpdateColumn}
|
|
119
|
+
onRemove={handleRemoveColumn}
|
|
120
|
+
onMoveUp={handleMoveColumnUp}
|
|
121
|
+
onMoveDown={handleMoveColumnDown}
|
|
122
|
+
/>
|
|
123
|
+
))}
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{/* Producer: Table with colgroup that registers columns */}
|
|
127
|
+
<TableProducer
|
|
128
|
+
state={state}
|
|
129
|
+
ColumnProducerProvider={ColumnProducerProvider}
|
|
130
|
+
/>
|
|
131
|
+
|
|
132
|
+
{/* Consumer: Components that read column data */}
|
|
133
|
+
<ColumnConsumers ColumnConsumerProvider={ColumnConsumerProvider} />
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Producer: Table with colgroup that registers columns
|
|
139
|
+
const TableProducer = ({ state, ColumnProducerProvider }) => {
|
|
140
|
+
return (
|
|
141
|
+
<ColumnProducerProvider>
|
|
142
|
+
<div className="section">
|
|
143
|
+
<h3>🏗️ Producer: Table with Column Registration</h3>
|
|
144
|
+
<p style={{ fontSize: "12px", margin: "0 0 10px 0", color: "#666" }}>
|
|
145
|
+
💡 The colgroup below registers column definitions. Change widths to
|
|
146
|
+
trigger individual column re-renders.
|
|
147
|
+
</p>
|
|
148
|
+
<table style={{ width: "100%", borderCollapse: "collapse" }}>
|
|
149
|
+
{/* This colgroup registers the columns - this is the PRODUCER */}
|
|
150
|
+
<colgroup>
|
|
151
|
+
{state.columns.map((column) => (
|
|
152
|
+
<ColumnDefinition
|
|
153
|
+
key={`${column.id}-${state.renderKey}`}
|
|
154
|
+
id={column.id}
|
|
155
|
+
label={column.label}
|
|
156
|
+
width={column.width}
|
|
157
|
+
sortable={column.sortable}
|
|
158
|
+
/>
|
|
159
|
+
))}
|
|
160
|
+
</colgroup>
|
|
161
|
+
<thead>
|
|
162
|
+
<tr>
|
|
163
|
+
{state.columns.map((column) => (
|
|
164
|
+
<th
|
|
165
|
+
key={column.id}
|
|
166
|
+
style={{
|
|
167
|
+
border: "1px solid #ddd",
|
|
168
|
+
padding: "8px",
|
|
169
|
+
background: "#f5f5f5",
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
{column.label}
|
|
173
|
+
{column.sortable && " ↕️"}
|
|
174
|
+
</th>
|
|
175
|
+
))}
|
|
176
|
+
</tr>
|
|
177
|
+
</thead>
|
|
178
|
+
<tbody>
|
|
179
|
+
{state.tableData.map((row, rowIndex) => (
|
|
180
|
+
<tr key={rowIndex}>
|
|
181
|
+
{state.columns.map((column) => (
|
|
182
|
+
<td
|
|
183
|
+
key={column.id}
|
|
184
|
+
style={{ border: "1px solid #ddd", padding: "8px" }}
|
|
185
|
+
>
|
|
186
|
+
{row[column.id] || `row${rowIndex}-${column.id}`}
|
|
187
|
+
</td>
|
|
188
|
+
))}
|
|
189
|
+
</tr>
|
|
190
|
+
))}
|
|
191
|
+
</tbody>
|
|
192
|
+
</table>
|
|
193
|
+
</div>
|
|
194
|
+
</ColumnProducerProvider>
|
|
195
|
+
);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Consumer: Components that read column data
|
|
199
|
+
const ColumnConsumers = ({ ColumnConsumerProvider }) => {
|
|
200
|
+
return (
|
|
201
|
+
<ColumnConsumerProvider>
|
|
202
|
+
<div className="section">
|
|
203
|
+
<h3>👁️ Consumers: Components Reading Column Data</h3>
|
|
204
|
+
<p style={{ fontSize: "12px", margin: "0 0 10px 0", color: "#666" }}>
|
|
205
|
+
These components read column data by index. They re-render when column
|
|
206
|
+
data changes.
|
|
207
|
+
</p>
|
|
208
|
+
<div style={{ display: "flex", flexWrap: "wrap", gap: "10px" }}>
|
|
209
|
+
<ColumnConsumer columnIndex={0} />
|
|
210
|
+
<ColumnConsumer columnIndex={1} />
|
|
211
|
+
<ColumnConsumer columnIndex={2} />
|
|
212
|
+
<ColumnConsumer columnIndex={3} />
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
<div className="section">
|
|
217
|
+
<h3>📊 All Tracked Columns</h3>
|
|
218
|
+
<AllColumnsDisplay />
|
|
219
|
+
</div>
|
|
220
|
+
</ColumnConsumerProvider>
|
|
221
|
+
);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// Column definition component with local state for testing overrides
|
|
225
|
+
const ColumnDefinition = ({ id, label, width: initialWidth, sortable }) => {
|
|
226
|
+
const [localWidth, setLocalWidth] = useState(initialWidth);
|
|
227
|
+
const [localSortable, setLocalSortable] = useState(sortable);
|
|
228
|
+
const outWidthRef = useRef(initialWidth);
|
|
229
|
+
const outSortableRef = useRef(sortable);
|
|
230
|
+
|
|
231
|
+
// Reset local state when props change
|
|
232
|
+
if (outWidthRef.current !== initialWidth) {
|
|
233
|
+
outWidthRef.current = initialWidth;
|
|
234
|
+
setLocalWidth(initialWidth);
|
|
235
|
+
}
|
|
236
|
+
if (outSortableRef.current !== sortable) {
|
|
237
|
+
outSortableRef.current = sortable;
|
|
238
|
+
setLocalSortable(sortable);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const [renderKey, setRenderKey] = useState(0);
|
|
242
|
+
|
|
243
|
+
// Register the column with current local state
|
|
244
|
+
const columnIndex = useRegisterColumn({
|
|
245
|
+
id,
|
|
246
|
+
label,
|
|
247
|
+
width: localWidth,
|
|
248
|
+
sortable: localSortable,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
log(`�️ Column producer ${id} (index ${columnIndex}) render`);
|
|
252
|
+
|
|
253
|
+
const widthOptions = ["100px", "150px", "200px", "250px", "300px"];
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<>
|
|
257
|
+
<col style={{ width: localWidth }} />
|
|
258
|
+
<div className="column-producer-controls">
|
|
259
|
+
<div className="item-info">
|
|
260
|
+
<strong>Column Producer {columnIndex}:</strong> id={id}, label={label}
|
|
261
|
+
, width={localWidth}, sortable={localSortable ? "yes" : "no"}
|
|
262
|
+
<small> (render #{renderKey})</small>
|
|
263
|
+
</div>
|
|
264
|
+
<div className="item-controls">
|
|
265
|
+
<select
|
|
266
|
+
value={localWidth}
|
|
267
|
+
onChange={(e) => {
|
|
268
|
+
log(
|
|
269
|
+
`📏 Column ${id} width changed to ${e.target.value} (local override)`,
|
|
270
|
+
);
|
|
271
|
+
setLocalWidth(e.target.value);
|
|
272
|
+
}}
|
|
273
|
+
>
|
|
274
|
+
{widthOptions.map((width) => (
|
|
275
|
+
<option key={width} value={width}>
|
|
276
|
+
{width}
|
|
277
|
+
</option>
|
|
278
|
+
))}
|
|
279
|
+
</select>
|
|
280
|
+
<label style={{ marginLeft: "10px" }}>
|
|
281
|
+
<input
|
|
282
|
+
type="checkbox"
|
|
283
|
+
checked={localSortable}
|
|
284
|
+
onChange={(e) => {
|
|
285
|
+
log(`🔄 Column ${id} sortable changed to ${e.target.checked}`);
|
|
286
|
+
setLocalSortable(e.target.checked);
|
|
287
|
+
}}
|
|
288
|
+
/>
|
|
289
|
+
Sortable
|
|
290
|
+
</label>
|
|
291
|
+
<button
|
|
292
|
+
className="rerender"
|
|
293
|
+
onClick={() => {
|
|
294
|
+
log(`🔄 Re-rendering column producer ${id}`);
|
|
295
|
+
setRenderKey((prev) => prev + 1);
|
|
296
|
+
}}
|
|
297
|
+
>
|
|
298
|
+
Re-render Producer
|
|
299
|
+
</button>
|
|
300
|
+
<button
|
|
301
|
+
onClick={() => {
|
|
302
|
+
log(`🔄 Reset column ${id} to original state`);
|
|
303
|
+
setLocalWidth(initialWidth);
|
|
304
|
+
setLocalSortable(sortable);
|
|
305
|
+
}}
|
|
306
|
+
>
|
|
307
|
+
Reset
|
|
308
|
+
</button>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
</>
|
|
312
|
+
);
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// Column data controls that affect the global state
|
|
316
|
+
const ColumnDataControls = ({
|
|
317
|
+
column,
|
|
318
|
+
index,
|
|
319
|
+
totalColumns,
|
|
320
|
+
onUpdate,
|
|
321
|
+
onRemove,
|
|
322
|
+
onMoveUp,
|
|
323
|
+
onMoveDown,
|
|
324
|
+
}) => {
|
|
325
|
+
const widthOptions = ["100px", "150px", "200px", "250px", "300px"];
|
|
326
|
+
const isFirst = index === 0;
|
|
327
|
+
const isLast = index === totalColumns - 1;
|
|
328
|
+
|
|
329
|
+
return (
|
|
330
|
+
<div className="item-data">
|
|
331
|
+
<div className="item-info">
|
|
332
|
+
<strong>App Column {index}:</strong> id={column.id}, label=
|
|
333
|
+
{column.label}, width={column.width}, sortable=
|
|
334
|
+
{column.sortable ? "yes" : "no"}
|
|
335
|
+
</div>
|
|
336
|
+
<div className="item-controls">
|
|
337
|
+
<button
|
|
338
|
+
onClick={() => onMoveUp(index)}
|
|
339
|
+
disabled={isFirst}
|
|
340
|
+
title={isFirst ? "Already at top" : "Move up"}
|
|
341
|
+
style={{
|
|
342
|
+
background: isFirst ? "#ccc" : "#2196f3",
|
|
343
|
+
cursor: isFirst ? "not-allowed" : "pointer",
|
|
344
|
+
}}
|
|
345
|
+
>
|
|
346
|
+
⬆️
|
|
347
|
+
</button>
|
|
348
|
+
<button
|
|
349
|
+
onClick={() => onMoveDown(index)}
|
|
350
|
+
disabled={isLast}
|
|
351
|
+
title={isLast ? "Already at bottom" : "Move down"}
|
|
352
|
+
style={{
|
|
353
|
+
background: isLast ? "#ccc" : "#2196f3",
|
|
354
|
+
cursor: isLast ? "not-allowed" : "pointer",
|
|
355
|
+
}}
|
|
356
|
+
>
|
|
357
|
+
⬇️
|
|
358
|
+
</button>
|
|
359
|
+
<input
|
|
360
|
+
value={column.label}
|
|
361
|
+
onChange={(e) =>
|
|
362
|
+
onUpdate(index, { ...column, label: e.target.value })
|
|
363
|
+
}
|
|
364
|
+
placeholder="Column label"
|
|
365
|
+
style={{ marginRight: "5px" }}
|
|
366
|
+
/>
|
|
367
|
+
<select
|
|
368
|
+
value={column.width}
|
|
369
|
+
onChange={(e) =>
|
|
370
|
+
onUpdate(index, { ...column, width: e.target.value })
|
|
371
|
+
}
|
|
372
|
+
>
|
|
373
|
+
{widthOptions.map((width) => (
|
|
374
|
+
<option key={width} value={width}>
|
|
375
|
+
{width}
|
|
376
|
+
</option>
|
|
377
|
+
))}
|
|
378
|
+
</select>
|
|
379
|
+
<label style={{ margin: "0 10px" }}>
|
|
380
|
+
<input
|
|
381
|
+
type="checkbox"
|
|
382
|
+
checked={column.sortable}
|
|
383
|
+
onChange={(e) =>
|
|
384
|
+
onUpdate(index, { ...column, sortable: e.target.checked })
|
|
385
|
+
}
|
|
386
|
+
/>
|
|
387
|
+
Sortable
|
|
388
|
+
</label>
|
|
389
|
+
<button className="remove" onClick={() => onRemove(index)}>
|
|
390
|
+
Remove
|
|
391
|
+
</button>
|
|
392
|
+
</div>
|
|
393
|
+
</div>
|
|
394
|
+
);
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// Column consumer with re-render button
|
|
398
|
+
const ColumnConsumer = ({ columnIndex }) => {
|
|
399
|
+
const [renderKey, setRenderKey] = useState(0);
|
|
400
|
+
const columnData = useColumn(columnIndex);
|
|
401
|
+
log(`👁️ Consumer ${columnIndex} render, ${columnData?.label}`);
|
|
402
|
+
|
|
403
|
+
return (
|
|
404
|
+
<div className="item-consumer">
|
|
405
|
+
<div>
|
|
406
|
+
<strong>Consumer for column {columnIndex}:</strong>
|
|
407
|
+
<div style={{ fontSize: "11px", marginTop: "4px" }}>
|
|
408
|
+
{columnData ? (
|
|
409
|
+
<div>
|
|
410
|
+
<div>ID: {columnData.id}</div>
|
|
411
|
+
<div>Label: {columnData.label}</div>
|
|
412
|
+
<div>Width: {columnData.width}</div>
|
|
413
|
+
<div>Sortable: {columnData.sortable ? "yes" : "no"}</div>
|
|
414
|
+
</div>
|
|
415
|
+
) : (
|
|
416
|
+
"undefined"
|
|
417
|
+
)}
|
|
418
|
+
</div>
|
|
419
|
+
<small> (render #{renderKey})</small>
|
|
420
|
+
</div>
|
|
421
|
+
<button
|
|
422
|
+
className="rerender"
|
|
423
|
+
onClick={() => {
|
|
424
|
+
log(`🔄 Re-rendering column consumer ${columnIndex}`);
|
|
425
|
+
setRenderKey((prev) => prev + 1);
|
|
426
|
+
}}
|
|
427
|
+
>
|
|
428
|
+
Re-render
|
|
429
|
+
</button>
|
|
430
|
+
</div>
|
|
431
|
+
);
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
// All columns display
|
|
435
|
+
const AllColumnsDisplay = () => {
|
|
436
|
+
const columns = useColumns();
|
|
437
|
+
return (
|
|
438
|
+
<div
|
|
439
|
+
style={{
|
|
440
|
+
background: "#fff3e0",
|
|
441
|
+
padding: "10px",
|
|
442
|
+
borderRadius: "4px",
|
|
443
|
+
}}
|
|
444
|
+
>
|
|
445
|
+
<strong>All Tracked Columns ({columns.length}):</strong>
|
|
446
|
+
<pre style={{ fontSize: "11px", margin: "8px 0 0 0" }}>
|
|
447
|
+
{JSON.stringify(columns, null, 2)}
|
|
448
|
+
</pre>
|
|
449
|
+
</div>
|
|
450
|
+
);
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
// Debug logging
|
|
454
|
+
const log = (message) => {
|
|
455
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
456
|
+
const logElement = document.getElementById("logs");
|
|
457
|
+
logElement.textContent += `[${timestamp}] ${message}\n`;
|
|
458
|
+
logElement.scrollTop = logElement.scrollHeight;
|
|
459
|
+
console.log(message);
|
|
460
|
+
};
|