@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.
Files changed (139) hide show
  1. package/dist/jsenv_navi.js +22959 -0
  2. package/index.js +66 -16
  3. package/package.json +23 -11
  4. package/src/actions.js +50 -26
  5. package/src/browser_integration/browser_integration.js +31 -6
  6. package/src/browser_integration/via_history.js +42 -9
  7. package/src/components/action_execution/render_actionable_component.jsx +6 -4
  8. package/src/components/action_execution/use_action.js +51 -282
  9. package/src/components/action_execution/use_execute_action.js +106 -92
  10. package/src/components/action_execution/use_run_on_mount.js +9 -0
  11. package/src/components/action_renderer.jsx +21 -32
  12. package/src/components/demos/0_button_demo.html +574 -103
  13. package/src/components/demos/10_column_reordering_debug.html +277 -0
  14. package/src/components/demos/11_table_selection_debug.html +432 -0
  15. package/src/components/demos/1_checkbox_demo.html +579 -202
  16. package/src/components/demos/2_input_textual_demo.html +81 -138
  17. package/src/components/demos/3_radio_demo.html +0 -2
  18. package/src/components/demos/4_select_demo.html +19 -23
  19. package/src/components/demos/6_tablist_demo.html +77 -0
  20. package/src/components/demos/7_table_selection_demo.html +176 -0
  21. package/src/components/demos/8_table_fixed_headers_demo.html +584 -0
  22. package/src/components/demos/9_table_column_drag_demo.html +325 -0
  23. package/src/components/demos/action/0_button_demo.html +2 -4
  24. package/src/components/demos/action/1_input_text_demo.html +643 -222
  25. package/src/components/demos/action/3_details_demo.html +146 -115
  26. package/src/components/demos/action/4_input_checkbox_demo.html +442 -322
  27. package/src/components/demos/action/5_input_checkbox_state_demo.html +270 -0
  28. package/src/components/demos/action/6_checkbox_list_demo.html +304 -72
  29. package/src/components/demos/action/7_radio_list_demo.html +310 -170
  30. package/src/components/demos/action/{8_editable_text_demo.html → 8_editable_demo.html} +65 -76
  31. package/src/components/demos/action/9_link_demo.html +84 -62
  32. package/src/components/demos/ui_transition/0_action_renderer_ui_transition_demo.html +695 -0
  33. package/src/components/demos/ui_transition/1_nested_ui_transition_demo.html +429 -0
  34. package/src/components/demos/ui_transition/2_height_transition_test.html +295 -0
  35. package/src/components/details/details.jsx +62 -64
  36. package/src/components/edition/editable.jsx +186 -0
  37. package/src/components/field/README.md +247 -0
  38. package/src/components/{input → field}/button.jsx +151 -130
  39. package/src/components/field/checkbox_list.jsx +184 -0
  40. package/src/components/{collect_form_element_values.js → field/collect_form_element_values.js} +7 -4
  41. package/src/components/{input → field}/field_css.js +4 -1
  42. package/src/components/field/form.jsx +211 -0
  43. package/src/components/{input → field}/input.jsx +1 -0
  44. package/src/components/{input → field}/input_checkbox.jsx +132 -155
  45. package/src/components/{input → field}/input_radio.jsx +135 -46
  46. package/src/components/field/input_textual.jsx +418 -0
  47. package/src/components/field/label.jsx +32 -0
  48. package/src/components/field/radio_list.jsx +182 -0
  49. package/src/components/{input → field}/select.jsx +17 -32
  50. package/src/components/field/use_action_events.js +132 -0
  51. package/src/components/field/use_form_events.js +55 -0
  52. package/src/components/field/use_ui_state_controller.js +506 -0
  53. package/src/components/item_tracker/README.md +461 -0
  54. package/src/components/item_tracker/use_isolated_item_tracker.jsx +209 -0
  55. package/src/components/item_tracker/use_isolated_item_tracker_demo.html +148 -0
  56. package/src/components/item_tracker/use_isolated_item_tracker_demo.jsx +460 -0
  57. package/src/components/item_tracker/use_item_tracker.jsx +143 -0
  58. package/src/components/item_tracker/use_item_tracker_demo.html +207 -0
  59. package/src/components/item_tracker/use_item_tracker_demo.jsx +216 -0
  60. package/src/components/keyboard_shortcuts/active_keyboard_shortcuts.jsx +87 -0
  61. package/src/components/keyboard_shortcuts/aria_key_shortcuts.js +61 -0
  62. package/src/components/keyboard_shortcuts/keyboard_key_meta.js +17 -0
  63. package/src/components/keyboard_shortcuts/keyboard_shortcuts.js +371 -0
  64. package/src/components/link/link.jsx +65 -102
  65. package/src/components/link/link_with_icon.jsx +52 -0
  66. package/src/components/loader/loader_background.jsx +85 -64
  67. package/src/components/loader/rectangle_loading.jsx +38 -19
  68. package/src/components/route.jsx +8 -4
  69. package/src/components/selection/selection.jsx +1583 -0
  70. package/src/components/svg/font_sized_svg.jsx +45 -0
  71. package/src/components/svg/icon_and_text.jsx +21 -0
  72. package/src/components/svg/svg_mask_overlay.jsx +105 -0
  73. package/src/components/table/drag/table_drag.jsx +506 -0
  74. package/src/components/table/resize/table_resize.jsx +650 -0
  75. package/src/components/table/resize/table_size.js +43 -0
  76. package/src/components/table/selection/table_selection.js +106 -0
  77. package/src/components/table/selection/table_selection.jsx +203 -0
  78. package/src/components/table/sticky/sticky_group.js +354 -0
  79. package/src/components/table/sticky/table_sticky.js +25 -0
  80. package/src/components/table/sticky/table_sticky.jsx +501 -0
  81. package/src/components/table/table.jsx +721 -0
  82. package/src/components/table/table_css.js +211 -0
  83. package/src/components/table/table_ui.jsx +49 -0
  84. package/src/components/table/use_cells_and_columns.js +90 -0
  85. package/src/components/table/use_object_array_to_cells.js +46 -0
  86. package/src/components/table/z_indexes.js +23 -0
  87. package/src/components/tablist/tablist.jsx +99 -0
  88. package/src/components/text/overflow.jsx +15 -0
  89. package/src/components/text/text_and_count.jsx +28 -0
  90. package/src/components/ui_transition.jsx +128 -0
  91. package/src/components/use_auto_focus.js +58 -7
  92. package/src/components/use_batch_during_render.js +33 -0
  93. package/src/components/use_debounce_true.js +7 -7
  94. package/src/components/use_dependencies_diff.js +35 -0
  95. package/src/components/use_focus_group.js +4 -3
  96. package/src/components/use_initial_value.js +8 -34
  97. package/src/components/use_signal_sync.js +1 -1
  98. package/src/components/use_stable_callback.js +68 -0
  99. package/src/components/use_state_array.js +16 -9
  100. package/src/docs/actions.md +22 -0
  101. package/src/notes.md +33 -12
  102. package/src/route/route.js +97 -47
  103. package/src/store/resource_graph.js +2 -1
  104. package/src/store/tests/{resource_graph_dependencies.test.js → resource_graph_dependencies.test_manual.js} +13 -13
  105. package/src/utils/is_signal.js +20 -0
  106. package/src/utils/stringify_for_display.js +4 -23
  107. package/src/validation/constraints/confirm_constraint.js +14 -0
  108. package/src/validation/constraints/create_unique_value_constraint.js +27 -0
  109. package/src/validation/constraints/native_constraints.js +313 -0
  110. package/src/validation/constraints/readonly_constraint.js +36 -0
  111. package/src/validation/constraints/single_space_constraint.js +13 -0
  112. package/src/validation/custom_constraint_validation.js +599 -0
  113. package/src/validation/custom_message.js +18 -0
  114. package/src/validation/demos/browser_style.png +0 -0
  115. package/src/validation/demos/form_validation_demo.html +142 -0
  116. package/src/validation/demos/form_validation_demo_preact.html +87 -0
  117. package/src/validation/demos/form_validation_native_popover_demo.html +168 -0
  118. package/src/validation/demos/form_validation_vs_native_demo.html +172 -0
  119. package/src/validation/demos/validation_message_demo.html +203 -0
  120. package/src/validation/hooks/use_constraints.js +23 -0
  121. package/src/validation/hooks/use_custom_validation_ref.js +73 -0
  122. package/src/validation/hooks/use_validation_message.js +19 -0
  123. package/src/validation/validation_message.js +741 -0
  124. package/src/components/editable_text/editable_text.jsx +0 -96
  125. package/src/components/form.jsx +0 -144
  126. package/src/components/input/checkbox_list.jsx +0 -294
  127. package/src/components/input/field.jsx +0 -61
  128. package/src/components/input/input_textual.jsx +0 -338
  129. package/src/components/input/radio_list.jsx +0 -283
  130. package/src/components/input/use_form_event.js +0 -20
  131. package/src/components/input/use_on_change.js +0 -12
  132. package/src/components/selection/selection.js +0 -5
  133. package/src/components/selection/selection_context.jsx +0 -262
  134. package/src/components/shortcut/shortcut_context.jsx +0 -390
  135. package/src/components/use_action_events.js +0 -37
  136. package/src/utils/iterable_weak_set.js +0 -62
  137. /package/src/components/demos/action/{11_nested_shortcuts_demo.html → 11_nested_shortcuts_demo.xhtml} +0 -0
  138. /package/src/components/{shortcut → keyboard_shortcuts}/os.js +0 -0
  139. /package/src/route/{route.test.html → route.xtest.html} +0 -0
@@ -5,25 +5,54 @@ import { useDebounceTrue } from "../use_debounce_true.js";
5
5
  import { RectangleLoading } from "./rectangle_loading.jsx";
6
6
 
7
7
  import.meta.css = /* css */ `
8
- [name="element_with_loader_wrapper"] {
9
- display: inline-flex;
8
+ .navi_inline_wrapper {
10
9
  position: relative;
11
10
  width: fit-content;
11
+ display: inline-flex;
12
+ height: fit-content;
12
13
  }
13
14
 
14
- [name="loading_rectangle_wrapper"] {
15
+ .navi_loading_rectangle_wrapper {
15
16
  pointer-events: none;
16
17
  position: absolute;
17
18
  z-index: 1;
19
+ opacity: 0;
20
+ top: var(--rectangle-top, 0);
21
+ left: var(--rectangle-left, 0);
22
+ bottom: var(--rectangle-bottom, 0);
23
+ right: var(--rectangle-right, 0);
18
24
  }
19
-
20
- [name="rectangle_loading"] {
21
- position: relative;
22
- width: 100%;
23
- height: 100%;
25
+ .navi_loading_rectangle_wrapper[data-visible] {
26
+ opacity: 1;
24
27
  }
25
28
  `;
26
29
 
30
+ export const LoadableInlineElement = ({
31
+ children,
32
+ width,
33
+ height,
34
+ ...props
35
+ }) => {
36
+ const actionName = props["data-action"];
37
+ if (actionName) {
38
+ delete props["data-action"];
39
+ }
40
+
41
+ return (
42
+ <span
43
+ className="navi_inline_wrapper"
44
+ style={{
45
+ ...(width ? { width } : {}),
46
+ ...(height ? { height } : {}),
47
+ }}
48
+ data-action={actionName}
49
+ >
50
+ <LoaderBackground {...props} />
51
+ {children}
52
+ </span>
53
+ );
54
+ };
55
+
27
56
  export const LoaderBackground = ({
28
57
  loading,
29
58
  containerRef,
@@ -59,7 +88,7 @@ export const LoaderBackground = ({
59
88
  }
60
89
 
61
90
  return (
62
- <LoaderBackgroundWithWrapper
91
+ <LoaderBackgroundBasic
63
92
  targetSelector={targetSelector}
64
93
  loading={loading}
65
94
  color={color}
@@ -70,7 +99,7 @@ export const LoaderBackground = ({
70
99
  spacingRight={spacingRight}
71
100
  >
72
101
  {children}
73
- </LoaderBackgroundWithWrapper>
102
+ </LoaderBackgroundBasic>
74
103
  );
75
104
  };
76
105
 
@@ -115,7 +144,7 @@ const LoaderBackgroundWithPortal = ({
115
144
  );
116
145
  };
117
146
 
118
- const LoaderBackgroundWithWrapper = ({
147
+ const LoaderBackgroundBasic = ({
119
148
  loading,
120
149
  targetSelector,
121
150
  color,
@@ -127,8 +156,8 @@ const LoaderBackgroundWithWrapper = ({
127
156
  children,
128
157
  }) => {
129
158
  const shouldShowSpinner = useDebounceTrue(loading, 300);
130
- const containerRef = useRef(null);
131
- const [outlineOffset, setOutlineOffset] = useState(0);
159
+ const rectangleRef = useRef(null);
160
+ const [, setOutlineOffset] = useState(0);
132
161
  const [borderRadius, setBorderRadius] = useState(0);
133
162
  const [borderTopWidth, setBorderTopWidth] = useState(0);
134
163
  const [borderLeftWidth, setBorderLeftWidth] = useState(0);
@@ -142,17 +171,18 @@ const LoaderBackgroundWithWrapper = ({
142
171
  const [paddingLeft, setPaddingLeft] = useState(0);
143
172
  const [paddingRight, setPaddingRight] = useState(0);
144
173
  const [paddingBottom, setPaddingBottom] = useState(0);
145
- const [flexGrow, setFlexGrow] = useState(0);
146
- const [flexShrink, setFlexShrink] = useState(1);
147
- const [flexBasis, setFlexBasis] = useState("auto");
148
174
 
149
175
  const [currentColor, setCurrentColor] = useState(color);
150
176
 
151
177
  useLayoutEffect(() => {
152
178
  let animationFrame;
153
179
  const updateStyles = () => {
154
- const container = containerRef.current;
155
- const containedElement = container.lastElementChild;
180
+ const rectangle = rectangleRef.current;
181
+ if (!rectangle) {
182
+ return;
183
+ }
184
+ const container = rectangle.parentElement;
185
+ const containedElement = rectangle.nextElementSibling;
156
186
  const target = targetSelector
157
187
  ? container.querySelector(targetSelector)
158
188
  : containedElement;
@@ -163,11 +193,6 @@ const LoaderBackgroundWithWrapper = ({
163
193
  window.getComputedStyle(containedElement);
164
194
  const targetComputedStyle = window.getComputedStyle(target);
165
195
 
166
- // Read flex properties from the contained element to mirror its behavior
167
- const newFlexGrow = containedComputedStyle.flexGrow || "0";
168
- const newFlexShrink = containedComputedStyle.flexShrink || "1";
169
- const newFlexBasis = containedComputedStyle.flexBasis || "auto";
170
-
171
196
  const newBorderTopWidth = resolveCSSSize(
172
197
  targetComputedStyle.borderTopWidth,
173
198
  );
@@ -222,9 +247,6 @@ const LoaderBackgroundWithWrapper = ({
222
247
  setPaddingLeft(paddingLeft);
223
248
  setPaddingRight(paddingRight);
224
249
  setPaddingBottom(paddingBottom);
225
- setFlexGrow(newFlexGrow);
226
- setFlexShrink(newFlexShrink);
227
- setFlexBasis(newFlexBasis);
228
250
 
229
251
  if (color) {
230
252
  setCurrentColor(color);
@@ -241,7 +263,6 @@ const LoaderBackgroundWithWrapper = ({
241
263
  setCurrentColor(newDetectedColor);
242
264
  }
243
265
  }
244
-
245
266
  // updateStyles is very cheap so we run it every frame
246
267
  animationFrame = requestAnimationFrame(updateStyles);
247
268
  };
@@ -253,20 +274,20 @@ const LoaderBackgroundWithWrapper = ({
253
274
  }, [color, targetSelector]);
254
275
 
255
276
  spacingTop += inset;
256
- spacingTop += outlineOffset;
257
- spacingTop -= borderTopWidth;
277
+ // spacingTop += outlineOffset;
278
+ // spacingTop -= borderTopWidth;
258
279
  spacingTop += marginTop;
259
280
  spacingLeft += inset;
260
- spacingLeft += outlineOffset;
261
- spacingLeft -= borderLeftWidth;
281
+ // spacingLeft += outlineOffset;
282
+ // spacingLeft -= borderLeftWidth;
262
283
  spacingLeft += marginLeft;
263
284
  spacingRight += inset;
264
- spacingRight += outlineOffset;
265
- spacingRight -= borderRightWidth;
285
+ // spacingRight += outlineOffset;
286
+ // spacingRight -= borderRightWidth;
266
287
  spacingRight += marginRight;
267
288
  spacingBottom += inset;
268
- spacingBottom += outlineOffset;
269
- spacingBottom -= borderBottomWidth;
289
+ // spacingBottom += outlineOffset;
290
+ // spacingBottom -= borderBottomWidth;
270
291
  spacingBottom += marginBottom;
271
292
  if (targetSelector) {
272
293
  // oversimplification that actually works
@@ -283,42 +304,42 @@ const LoaderBackgroundWithWrapper = ({
283
304
  borderRightWidth,
284
305
  borderBottomWidth,
285
306
  );
286
- const size = Math.max(2, maxBorderWidth / 2);
287
-
288
- spacingTop += size / 4;
289
- spacingLeft += size / 4;
290
- spacingRight += size / 4;
291
- spacingBottom += size / 4;
307
+ const halfMaxBorderSize = maxBorderWidth / 2;
308
+ const size = halfMaxBorderSize < 2 ? 2 : halfMaxBorderSize;
309
+ const lineHalfSize = size / 2;
310
+ spacingTop -= lineHalfSize;
311
+ spacingLeft -= lineHalfSize;
312
+ spacingRight -= lineHalfSize;
313
+ spacingBottom -= lineHalfSize;
292
314
 
293
315
  return (
294
- <div
295
- name="element_with_loader_wrapper"
296
- ref={containerRef}
297
- data-loader-visible={shouldShowSpinner ? "" : undefined}
298
- style={{
299
- flexGrow,
300
- flexShrink,
301
- flexBasis,
302
- }}
303
- >
304
- {shouldShowSpinner && (
305
- <div
306
- name="loading_rectangle_wrapper"
307
- style={{
308
- top: `${spacingTop}px`,
309
- left: `${spacingLeft}px`,
310
- bottom: `${spacingBottom}px`,
311
- right: `${spacingRight}px`,
312
- }}
313
- >
316
+ <>
317
+ <span
318
+ ref={rectangleRef}
319
+ className="navi_loading_rectangle_wrapper"
320
+ data-visible={shouldShowSpinner ? "" : undefined}
321
+ style={{
322
+ "--rectangle-top": `${spacingTop}px`,
323
+ "--rectangle-left": `${spacingLeft}px`,
324
+ "--rectangle-bottom": `${spacingBottom}px`,
325
+ "--rectangle-right": `${spacingRight}px`,
326
+ }}
327
+ >
328
+ {/* We want to start rendering the loading asap
329
+ so it can start to rotate as soon as we start to load
330
+ This feels more natural when the loader appears with some initial rotation
331
+ correspondong to the time it took to display it. It feels like it was busy
332
+ And we don't display immeditaly in case it's very fast (<300ms) */}
333
+ {loading && (
314
334
  <RectangleLoading
335
+ shouldShowSpinner={shouldShowSpinner}
315
336
  color={currentColor}
316
337
  radius={borderRadius}
317
338
  size={size}
318
339
  />
319
- </div>
320
- )}
340
+ )}
341
+ </span>
321
342
  {children}
322
- </div>
343
+ </>
323
344
  );
324
345
  };
@@ -18,18 +18,41 @@
18
18
  import { useLayoutEffect, useRef, useState } from "preact/hooks";
19
19
  import { useNetworkSpeed } from "./network_speed.js";
20
20
 
21
+ import.meta.css = /* css */ `
22
+ .navi_rectangle_loading {
23
+ position: relative;
24
+ width: 100%;
25
+ height: 100%;
26
+ opacity: 0;
27
+ display: block;
28
+ }
29
+
30
+ .navi_rectangle_loading[data-visible] {
31
+ opacity: 1;
32
+ }
33
+ `;
34
+
21
35
  export const RectangleLoading = ({
36
+ shouldShowSpinner,
22
37
  color = "currentColor",
23
38
  radius = 0,
24
39
  size = 2,
25
40
  }) => {
26
41
  const containerRef = useRef(null);
27
- const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
42
+ const [containerWidth, setContainerWidth] = useState(0);
43
+ const [containerHeight, setContainerHeight] = useState(0);
28
44
 
29
45
  useLayoutEffect(() => {
30
46
  const container = containerRef.current;
31
- let animationFrameId = null;
47
+ if (!container) {
48
+ return null;
49
+ }
50
+
51
+ const { width, height } = container.getBoundingClientRect();
52
+ setContainerWidth(width);
53
+ setContainerHeight(height);
32
54
 
55
+ let animationFrameId = null;
33
56
  // Create a resize observer to detect changes in the container's dimensions
34
57
  const resizeObserver = new ResizeObserver((entries) => {
35
58
  // Use requestAnimationFrame to debounce updates
@@ -38,21 +61,13 @@ export const RectangleLoading = ({
38
61
  }
39
62
 
40
63
  animationFrameId = requestAnimationFrame(() => {
41
- for (const entry of entries) {
42
- const { width, height } = entry.contentRect;
43
- setDimensions({ width, height });
44
- }
64
+ const [containerEntry] = entries;
65
+ const { width, height } = containerEntry.contentRect;
66
+ setContainerWidth(width);
67
+ setContainerHeight(height);
45
68
  });
46
69
  });
47
-
48
70
  resizeObserver.observe(container);
49
-
50
- // Initial measurement
51
- setDimensions({
52
- width: container.offsetWidth,
53
- height: container.offsetHeight,
54
- });
55
-
56
71
  return () => {
57
72
  if (animationFrameId) {
58
73
  cancelAnimationFrame(animationFrameId);
@@ -62,17 +77,21 @@ export const RectangleLoading = ({
62
77
  }, []);
63
78
 
64
79
  return (
65
- <div name="rectangle_loading" ref={containerRef}>
66
- {dimensions.width > 0 && dimensions.height > 0 && (
80
+ <span
81
+ ref={containerRef}
82
+ className="navi_rectangle_loading"
83
+ data-visible={shouldShowSpinner ? "" : undefined}
84
+ >
85
+ {containerWidth > 0 && containerHeight > 0 && (
67
86
  <RectangleLoadingSvg
68
87
  radius={radius}
69
88
  color={color}
70
- width={dimensions.width}
71
- height={dimensions.height}
89
+ width={containerWidth}
90
+ height={containerHeight}
72
91
  strokeWidth={size}
73
92
  />
74
93
  )}
75
- </div>
94
+ </span>
76
95
  );
77
96
  };
78
97
 
@@ -1,5 +1,6 @@
1
1
  import { useRouteStatus } from "../route/route.js";
2
2
  import { ActionRenderer } from "./action_renderer.jsx";
3
+ import { useContentKey } from "./ui_transition.jsx";
3
4
 
4
5
  export const Route = ({ route, children }) => {
5
6
  if (!route.action) {
@@ -7,9 +8,12 @@ export const Route = ({ route, children }) => {
7
8
  "Route component requires a route with an action to render.",
8
9
  );
9
10
  }
10
- const { active } = useRouteStatus(route);
11
+ const { active, url } = useRouteStatus(route);
12
+ useContentKey(url, active);
11
13
 
12
- return active ? (
13
- <ActionRenderer action={route.action}>{children}</ActionRenderer>
14
- ) : null;
14
+ return (
15
+ <ActionRenderer disabled={!active} action={route.action}>
16
+ {children}
17
+ </ActionRenderer>
18
+ );
15
19
  };