@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
|
@@ -3,6 +3,27 @@
|
|
|
3
3
|
|
|
4
4
|
import { useEffect, useLayoutEffect } from "preact/hooks";
|
|
5
5
|
|
|
6
|
+
let blurEvent = null;
|
|
7
|
+
let timeout;
|
|
8
|
+
document.body.addEventListener(
|
|
9
|
+
"blur",
|
|
10
|
+
(e) => {
|
|
11
|
+
blurEvent = e;
|
|
12
|
+
setTimeout(() => {
|
|
13
|
+
blurEvent = null;
|
|
14
|
+
});
|
|
15
|
+
},
|
|
16
|
+
{ capture: true },
|
|
17
|
+
);
|
|
18
|
+
document.body.addEventListener(
|
|
19
|
+
"focus",
|
|
20
|
+
() => {
|
|
21
|
+
clearTimeout(timeout);
|
|
22
|
+
blurEvent = null;
|
|
23
|
+
},
|
|
24
|
+
{ capture: true },
|
|
25
|
+
);
|
|
26
|
+
|
|
6
27
|
export const useAutoFocus = (
|
|
7
28
|
focusableElementRef,
|
|
8
29
|
autoFocus,
|
|
@@ -21,19 +42,49 @@ export const useAutoFocus = (
|
|
|
21
42
|
focusableElement.scrollLeft = 0;
|
|
22
43
|
}
|
|
23
44
|
return () => {
|
|
24
|
-
|
|
45
|
+
const focusIsOnSelfOrInsideSelf =
|
|
25
46
|
document.activeElement === focusableElement ||
|
|
26
|
-
document.activeElement
|
|
47
|
+
focusableElement.contains(document.activeElement);
|
|
48
|
+
if (
|
|
49
|
+
!focusIsOnSelfOrInsideSelf &&
|
|
50
|
+
document.activeElement !== document.body
|
|
27
51
|
) {
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
52
|
+
// focus is not on our element (or body) anymore
|
|
53
|
+
// keep it where it is
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// We have focus but we are unmounted
|
|
58
|
+
// -> try to move focus back to something more meaningful that what browser would do
|
|
59
|
+
// (browser would put it to document.body)
|
|
60
|
+
// -> We'll try to move focus back to the element that had focus before we moved it to this element
|
|
61
|
+
|
|
62
|
+
if (!document.body.contains(activeElement)) {
|
|
63
|
+
// previously active element is no longer in the document
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (blurEvent) {
|
|
68
|
+
// But if this element is unmounted during a blur, the element that is about to receive focus should prevail
|
|
69
|
+
const elementAboutToReceiveFocus = blurEvent.relatedTarget;
|
|
70
|
+
const isSelfOrInsideSelf =
|
|
71
|
+
elementAboutToReceiveFocus === focusableElement ||
|
|
72
|
+
focusableElement.contains(elementAboutToReceiveFocus);
|
|
73
|
+
const isPreviouslyActiveElementOrInsideIt =
|
|
74
|
+
elementAboutToReceiveFocus === activeElement ||
|
|
75
|
+
(activeElement && activeElement.contains(elementAboutToReceiveFocus));
|
|
76
|
+
if (!isSelfOrInsideSelf && !isPreviouslyActiveElementOrInsideIt) {
|
|
77
|
+
// the element about to receive focus is not the input itself or inside it
|
|
78
|
+
// and is not the previously active element or inside it
|
|
79
|
+
// -> the element about to receive focus should prevail
|
|
80
|
+
return;
|
|
33
81
|
}
|
|
34
82
|
}
|
|
83
|
+
|
|
84
|
+
activeElement.focus();
|
|
35
85
|
};
|
|
36
86
|
}, []);
|
|
87
|
+
|
|
37
88
|
useEffect(() => {
|
|
38
89
|
if (autoFocus) {
|
|
39
90
|
const focusableElement = focusableElementRef.current;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useLayoutEffect, useMemo, useRef } from "preact/hooks";
|
|
2
|
+
|
|
3
|
+
import { useStableCallback } from "./use_stable_callback.js";
|
|
4
|
+
|
|
5
|
+
export const useBatchDuringRender = (callback) => {
|
|
6
|
+
const pendingCallArrayRef = useRef([]);
|
|
7
|
+
const pendingCallArray = pendingCallArrayRef.current;
|
|
8
|
+
|
|
9
|
+
callback = useStableCallback(callback);
|
|
10
|
+
const callbackWithBatching = useMemo(() => {
|
|
11
|
+
return (...args) => {
|
|
12
|
+
if (isRenderingRef.current) {
|
|
13
|
+
pendingCallArray.push(args);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
callback(...args);
|
|
17
|
+
};
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
const isRenderingRef = useRef();
|
|
21
|
+
isRenderingRef.current = true;
|
|
22
|
+
useLayoutEffect(() => {
|
|
23
|
+
isRenderingRef.current = false;
|
|
24
|
+
if (pendingCallArray.length === 0) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const copy = [...pendingCallArray];
|
|
28
|
+
pendingCallArray.length = 0;
|
|
29
|
+
callback(copy);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return callbackWithBatching;
|
|
33
|
+
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
|
2
2
|
|
|
3
3
|
export const useDebounceTrue = (value, delay = 300) => {
|
|
4
|
-
const [
|
|
4
|
+
const [debouncedTrue, setDebouncedTrue] = useState(false);
|
|
5
5
|
const timerRef = useRef(null);
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
// If value becomes true, start a timer
|
|
7
|
+
useLayoutEffect(() => {
|
|
8
|
+
// If value is true or becomes true, start a timer
|
|
9
9
|
if (value) {
|
|
10
10
|
timerRef.current = setTimeout(() => {
|
|
11
|
-
|
|
11
|
+
setDebouncedTrue(true);
|
|
12
12
|
}, delay);
|
|
13
13
|
} else {
|
|
14
14
|
// If value becomes false, clear any pending timer and immediately set to false
|
|
@@ -16,7 +16,7 @@ export const useDebounceTrue = (value, delay = 300) => {
|
|
|
16
16
|
clearTimeout(timerRef.current);
|
|
17
17
|
timerRef.current = null;
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
setDebouncedTrue(false);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// Cleanup function
|
|
@@ -27,5 +27,5 @@ export const useDebounceTrue = (value, delay = 300) => {
|
|
|
27
27
|
};
|
|
28
28
|
}, [value, delay]);
|
|
29
29
|
|
|
30
|
-
return
|
|
30
|
+
return debouncedTrue;
|
|
31
31
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* - Usage
|
|
3
|
+
* useEffect(() => {
|
|
4
|
+
* // here you want to know what has changed, causing useEffect to be called
|
|
5
|
+
* }, [name, value])
|
|
6
|
+
*
|
|
7
|
+
* const diff = useDependenciesDiff({ name, value })
|
|
8
|
+
* useEffect(() => {
|
|
9
|
+
* console.log('useEffect called because', diff)
|
|
10
|
+
* }, [name, value])
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { useMemo, useRef } from "preact/hooks";
|
|
14
|
+
|
|
15
|
+
export const useDependenciesDiff = (inputs) => {
|
|
16
|
+
const oldInputsRef = useRef(inputs);
|
|
17
|
+
const inputValuesArray = Object.values(inputs);
|
|
18
|
+
const inputKeysArray = Object.keys(inputs);
|
|
19
|
+
const diffRef = useRef();
|
|
20
|
+
useMemo(() => {
|
|
21
|
+
const oldInputs = oldInputsRef.current;
|
|
22
|
+
const diff = {};
|
|
23
|
+
for (const key of inputKeysArray) {
|
|
24
|
+
const previous = oldInputs[key];
|
|
25
|
+
const current = inputs[key];
|
|
26
|
+
if (previous !== current) {
|
|
27
|
+
diff[key] = { previous, current };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
diffRef.current = diff;
|
|
31
|
+
oldInputsRef.current = inputs;
|
|
32
|
+
}, inputValuesArray);
|
|
33
|
+
|
|
34
|
+
return diffRef.current;
|
|
35
|
+
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { initFocusGroup } from "@jsenv/dom";
|
|
2
2
|
import { useLayoutEffect } from "preact/hooks";
|
|
3
3
|
|
|
4
|
-
export const useFocusGroup = (
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
export const useFocusGroup = (
|
|
5
|
+
elementRef,
|
|
6
|
+
{ enabled = true, direction, skipTab, loop, name } = {},
|
|
7
|
+
) => {
|
|
7
8
|
useLayoutEffect(() => {
|
|
8
9
|
if (!enabled) {
|
|
9
10
|
return null;
|
|
@@ -64,41 +64,15 @@ export const useExternalValueSync = (
|
|
|
64
64
|
}
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
* @param {any} defaultValue - Final fallback value
|
|
74
|
-
* @param {Function} setValue - Function to call when value needs to be set
|
|
75
|
-
*
|
|
76
|
-
* @returns {any} The resolved initial value
|
|
77
|
-
*/
|
|
78
|
-
export const useInitialValue = (
|
|
79
|
-
name,
|
|
80
|
-
externalValue,
|
|
81
|
-
fallbackValue,
|
|
82
|
-
defaultValue,
|
|
83
|
-
setValue,
|
|
84
|
-
) => {
|
|
85
|
-
const initialValue = resolveInitialValue(
|
|
86
|
-
externalValue,
|
|
87
|
-
fallbackValue,
|
|
88
|
-
defaultValue,
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
// Set initial value on mount
|
|
92
|
-
const mountedRef = useRef(false);
|
|
93
|
-
if (!mountedRef.current) {
|
|
94
|
-
mountedRef.current = true;
|
|
95
|
-
if (name) {
|
|
96
|
-
setValue(initialValue);
|
|
97
|
-
}
|
|
67
|
+
const UNSET = {};
|
|
68
|
+
export const useInitialValue = (compute) => {
|
|
69
|
+
const initialValueRef = useRef(UNSET);
|
|
70
|
+
let initialValue = initialValueRef.current;
|
|
71
|
+
if (initialValue !== UNSET) {
|
|
72
|
+
return initialValue;
|
|
98
73
|
}
|
|
99
74
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
75
|
+
initialValue = compute();
|
|
76
|
+
initialValueRef.current = initialValue;
|
|
103
77
|
return initialValue;
|
|
104
78
|
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom hook creating a stable callback that doesn't trigger re-renders.
|
|
3
|
+
*
|
|
4
|
+
* PROBLEM: Parent components often forget to use useCallback, causing library
|
|
5
|
+
* components to re-render unnecessarily when receiving callback props.
|
|
6
|
+
*
|
|
7
|
+
* SOLUTION: Library components can use this hook to create stable callback
|
|
8
|
+
* references internally, making them defensive against parents who don't
|
|
9
|
+
* optimize their callbacks. This ensures library components don't force
|
|
10
|
+
* consumers to think about useCallback.
|
|
11
|
+
*
|
|
12
|
+
* USAGE:
|
|
13
|
+
* ```js
|
|
14
|
+
* // Parent component (consumer) - no useCallback needed
|
|
15
|
+
* const Parent = () => {
|
|
16
|
+
* const [count, setCount] = useState(0);
|
|
17
|
+
*
|
|
18
|
+
* // Parent naturally creates new function reference each render
|
|
19
|
+
* // (forgetting useCallback is common and shouldn't break performance)
|
|
20
|
+
* return <LibraryButton onClick={(e) => setCount(count + 1)} />;
|
|
21
|
+
* };
|
|
22
|
+
*
|
|
23
|
+
* // Library component - defensive against changing callbacks
|
|
24
|
+
* const LibraryButton = ({ onClick }) => {
|
|
25
|
+
* // ✅ Create stable reference from parent's potentially changing callback
|
|
26
|
+
* const stableClick = useStableCallback(onClick);
|
|
27
|
+
*
|
|
28
|
+
* // Internal expensive components won't re-render when parent updates
|
|
29
|
+
* return <ExpensiveInternalButton onClick={stableClick} />;
|
|
30
|
+
* };
|
|
31
|
+
*
|
|
32
|
+
* // Deep internal component gets stable reference
|
|
33
|
+
* const ExpensiveInternalButton = memo(({ onClick }) => {
|
|
34
|
+
* // This won't re-render when Parent's count changes
|
|
35
|
+
* // But onClick will always call the latest Parent callback
|
|
36
|
+
* return <button onClick={onClick}>Click me</button>;
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* Perfect for library components that need performance without burdening consumers.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import { useRef } from "preact/hooks";
|
|
44
|
+
|
|
45
|
+
export const useStableCallback = (callback, mapper) => {
|
|
46
|
+
const callbackRef = useRef();
|
|
47
|
+
callbackRef.current = callback;
|
|
48
|
+
const stableCallbackRef = useRef();
|
|
49
|
+
|
|
50
|
+
// Return original falsy value directly when callback is not a function
|
|
51
|
+
if (!callback) {
|
|
52
|
+
return callback;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const existingStableCallback = stableCallbackRef.current;
|
|
56
|
+
if (existingStableCallback) {
|
|
57
|
+
return existingStableCallback;
|
|
58
|
+
}
|
|
59
|
+
const stableCallback = (...args) => {
|
|
60
|
+
const currentCallback = callbackRef.current;
|
|
61
|
+
if (mapper) {
|
|
62
|
+
args = mapper(...args);
|
|
63
|
+
}
|
|
64
|
+
return currentCallback(...args);
|
|
65
|
+
};
|
|
66
|
+
stableCallbackRef.current = stableCallback;
|
|
67
|
+
return stableCallback;
|
|
68
|
+
};
|
|
@@ -1,28 +1,35 @@
|
|
|
1
|
-
import { useCallback, useState } from "preact/hooks";
|
|
1
|
+
import { useCallback, useRef, useState } from "preact/hooks";
|
|
2
2
|
import { addIntoArray, removeFromArray } from "../utils/array_add_remove.js";
|
|
3
3
|
import {
|
|
4
4
|
resolveInitialValue,
|
|
5
5
|
useExternalValueSync,
|
|
6
6
|
} from "./use_initial_value.js";
|
|
7
7
|
|
|
8
|
+
const FIRST_MOUNT = {};
|
|
8
9
|
export const useStateArray = (
|
|
9
|
-
externalValue
|
|
10
|
+
externalValue,
|
|
10
11
|
fallbackValue,
|
|
11
12
|
defaultValue = [],
|
|
12
13
|
) => {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
const initialValueRef = useRef(FIRST_MOUNT);
|
|
15
|
+
if (initialValueRef.current === FIRST_MOUNT) {
|
|
16
|
+
const initialValue = resolveInitialValue(
|
|
17
|
+
externalValue,
|
|
18
|
+
fallbackValue,
|
|
19
|
+
defaultValue,
|
|
20
|
+
);
|
|
21
|
+
initialValueRef.current = initialValue;
|
|
22
|
+
}
|
|
23
|
+
const initialValue = initialValueRef.current;
|
|
18
24
|
const [array, setArray] = useState(initialValue);
|
|
19
25
|
|
|
20
|
-
//
|
|
26
|
+
// Only sync external value changes if externalValue was explicitly provided
|
|
21
27
|
useExternalValueSync(externalValue, defaultValue, setArray, "state_array");
|
|
22
28
|
|
|
23
29
|
const add = useCallback((valueToAdd) => {
|
|
24
30
|
setArray((array) => {
|
|
25
|
-
|
|
31
|
+
const newArray = addIntoArray(array, valueToAdd);
|
|
32
|
+
return newArray;
|
|
26
33
|
});
|
|
27
34
|
}, []);
|
|
28
35
|
|
package/src/docs/actions.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
- ```js
|
|
14
14
|
|
|
15
15
|
```
|
|
16
|
+
|
|
16
17
|
- const getUserTemplate = createActionTemplate(async ({ userId }) => {
|
|
17
18
|
- const response = await fetch(`/api/users/${userId}`);
|
|
18
19
|
- return response.json();
|
|
@@ -20,6 +21,7 @@
|
|
|
20
21
|
- ```
|
|
21
22
|
|
|
22
23
|
```
|
|
24
|
+
|
|
23
25
|
-
|
|
24
26
|
- ### 🎯 **Action Instances**
|
|
25
27
|
- Stateful objects created from templates with specific parameters. Each unique parameter set
|
|
@@ -27,17 +29,20 @@
|
|
|
27
29
|
- ```js
|
|
28
30
|
|
|
29
31
|
```
|
|
32
|
+
|
|
30
33
|
- const userAction = getUserTemplate.instantiate({ userId: 123 });
|
|
31
34
|
- const status = useActionStatus(userAction); // { pending, data, error, ... }
|
|
32
35
|
- ```
|
|
33
36
|
|
|
34
37
|
```
|
|
38
|
+
|
|
35
39
|
-
|
|
36
40
|
- ### 🔄 **Action Proxies**
|
|
37
41
|
- Dynamic actions that react to signal changes, automatically reloading when parameters change.
|
|
38
42
|
- ```js
|
|
39
43
|
|
|
40
44
|
```
|
|
45
|
+
|
|
41
46
|
- const userProxy = createActionProxy(getUserTemplate, {
|
|
42
47
|
- userId: userIdSignal, // Signal - reactive
|
|
43
48
|
- includeProfile: true // Static - not reactive
|
|
@@ -46,6 +51,7 @@
|
|
|
46
51
|
- ```
|
|
47
52
|
|
|
48
53
|
```
|
|
54
|
+
|
|
49
55
|
-
|
|
50
56
|
- ## Loading States & Lifecycle
|
|
51
57
|
-
|
|
@@ -77,6 +83,7 @@
|
|
|
77
83
|
- ```js
|
|
78
84
|
|
|
79
85
|
```
|
|
86
|
+
|
|
80
87
|
- const baseAction = getUserTemplate.instantiate({ userId: 123 });
|
|
81
88
|
- const enrichedAction = baseAction.bindParams({ includeProfile: true });
|
|
82
89
|
- // Result: { userId: 123, includeProfile: true }
|
|
@@ -86,6 +93,7 @@
|
|
|
86
93
|
- ```
|
|
87
94
|
|
|
88
95
|
```
|
|
96
|
+
|
|
89
97
|
-
|
|
90
98
|
- ### 🎮 **Concurrent Loading Control**
|
|
91
99
|
- - Prevents duplicate requests for same resource
|
|
@@ -96,6 +104,7 @@
|
|
|
96
104
|
- ```js
|
|
97
105
|
|
|
98
106
|
```
|
|
107
|
+
|
|
99
108
|
- const actionTemplate = createActionTemplate(callback, {
|
|
100
109
|
- sideEffect: (params, loadParams) => {
|
|
101
110
|
- // Setup logic (analytics, subscriptions, etc.)
|
|
@@ -107,6 +116,7 @@
|
|
|
107
116
|
- ```
|
|
108
117
|
|
|
109
118
|
```
|
|
119
|
+
|
|
110
120
|
-
|
|
111
121
|
- ## Usage Patterns
|
|
112
122
|
-
|
|
@@ -114,6 +124,7 @@
|
|
|
114
124
|
- ```js
|
|
115
125
|
|
|
116
126
|
```
|
|
127
|
+
|
|
117
128
|
- const getUserAction = createActionTemplate(async ({ userId }) => {
|
|
118
129
|
- return await api.getUser(userId);
|
|
119
130
|
- });
|
|
@@ -128,11 +139,13 @@
|
|
|
128
139
|
- ```
|
|
129
140
|
|
|
130
141
|
```
|
|
142
|
+
|
|
131
143
|
-
|
|
132
144
|
- ### 🔄 **Reactive Data Loading**
|
|
133
145
|
- ```js
|
|
134
146
|
|
|
135
147
|
```
|
|
148
|
+
|
|
136
149
|
- const searchProxy = createActionProxy(searchTemplate, {
|
|
137
150
|
- query: searchSignal,
|
|
138
151
|
- filters: filtersSignal
|
|
@@ -141,11 +154,13 @@
|
|
|
141
154
|
- ```
|
|
142
155
|
|
|
143
156
|
```
|
|
157
|
+
|
|
144
158
|
-
|
|
145
159
|
- ### 📋 **Master-Detail Pattern**
|
|
146
160
|
- ```js
|
|
147
161
|
|
|
148
162
|
```
|
|
163
|
+
|
|
149
164
|
- const usersAction = getUsersTemplate.instantiate();
|
|
150
165
|
- const selectedUser = signal(null);
|
|
151
166
|
-
|
|
@@ -155,11 +170,13 @@
|
|
|
155
170
|
- ```
|
|
156
171
|
|
|
157
172
|
```
|
|
173
|
+
|
|
158
174
|
-
|
|
159
175
|
- ### 🏃 **Progressive Loading**
|
|
160
176
|
- ```js
|
|
161
177
|
|
|
162
178
|
```
|
|
179
|
+
|
|
163
180
|
- // Preload on hover, load on click
|
|
164
181
|
- <button
|
|
165
182
|
- onMouseEnter={() => action.preload()}
|
|
@@ -170,6 +187,7 @@
|
|
|
170
187
|
- ```
|
|
171
188
|
|
|
172
189
|
```
|
|
190
|
+
|
|
173
191
|
-
|
|
174
192
|
- ## Advanced Features
|
|
175
193
|
-
|
|
@@ -177,6 +195,7 @@
|
|
|
177
195
|
- ```js
|
|
178
196
|
|
|
179
197
|
```
|
|
198
|
+
|
|
180
199
|
- const actionTemplate = createActionTemplate(fetchUser, {
|
|
181
200
|
- computedDataSignal: computed(() => {
|
|
182
201
|
- const rawData = dataSignal.value;
|
|
@@ -186,11 +205,13 @@
|
|
|
186
205
|
- ```
|
|
187
206
|
|
|
188
207
|
```
|
|
208
|
+
|
|
189
209
|
-
|
|
190
210
|
- ### 🎨 **Async Rendering Support**
|
|
191
211
|
- ```js
|
|
192
212
|
|
|
193
213
|
```
|
|
214
|
+
|
|
194
215
|
- const actionTemplate = createActionTemplate(fetchData, {
|
|
195
216
|
- renderLoadedAsync: async () => {
|
|
196
217
|
- const { UserComponent } = await import('./UserComponent.js');
|
|
@@ -200,6 +221,7 @@
|
|
|
200
221
|
- ```
|
|
201
222
|
|
|
202
223
|
```
|
|
224
|
+
|
|
203
225
|
-
|
|
204
226
|
- ### 🛠️ **Debugging & Observability**
|
|
205
227
|
- Built-in debug mode with detailed logging of state transitions, loading coordination,
|
package/src/notes.md
CHANGED
|
@@ -1,13 +1,34 @@
|
|
|
1
|
-
-
|
|
1
|
+
- Drag to move row and column
|
|
2
|
+
|
|
3
|
+
(ideally we keep an empty "clone" in the table and we create a visually identic clone to drag)
|
|
4
|
+
|
|
5
|
+
sitcky prevents drag to re-order
|
|
6
|
+
I think we are starting to reach the limits of a table element
|
|
7
|
+
|
|
8
|
+
donc on va refaire mais avec des div comme ca on controle bien tout
|
|
9
|
+
de toute facon les dimensions des lignes et colonnes seront fixes
|
|
10
|
+
et je gagne pas grand chose a passer par la balise table
|
|
11
|
+
|
|
12
|
+
- Ideally the drag gesture should autoscroll once dragged element boundaries reach the scrollable parent (not the mouse)
|
|
13
|
+
|
|
14
|
+
- Can use shortcuts on table selection
|
|
15
|
+
- cmd + delete would delete rows/columns
|
|
16
|
+
on cells it does nothing but we'll be able to copy via keyboard to start
|
|
17
|
+
|
|
18
|
+
- Fixed first column and first row (overflow on the rest + it's fixed when there is a lof of content)
|
|
19
|
+
|
|
20
|
+
- Can delete a table row
|
|
2
21
|
|
|
3
|
-
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
- Pagination
|
|
22
|
+
- Can update a table row cell
|
|
23
|
+
|
|
24
|
+
- Can see table columns attributes
|
|
25
|
+
|
|
26
|
+
- Can update table column attributes
|
|
27
|
+
|
|
28
|
+
- Can remove table column
|
|
29
|
+
|
|
30
|
+
- Can move table column
|
|
31
|
+
|
|
32
|
+
- Pagination
|
|
33
|
+
|
|
34
|
+
- import.meta.css during build should use stylesheet to inject so that it puts an url instead of constructed stylesheet?
|