@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.
Files changed (138) hide show
  1. package/dist/jsenv_navi.js +22954 -0
  2. package/index.js +66 -16
  3. package/package.json +22 -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/{input → field}/input_textual.jsx +247 -173
  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/radio_list.jsx +0 -283
  129. package/src/components/input/use_form_event.js +0 -20
  130. package/src/components/input/use_on_change.js +0 -12
  131. package/src/components/selection/selection.js +0 -5
  132. package/src/components/selection/selection_context.jsx +0 -262
  133. package/src/components/shortcut/shortcut_context.jsx +0 -390
  134. package/src/components/use_action_events.js +0 -37
  135. package/src/utils/iterable_weak_set.js +0 -62
  136. /package/src/components/demos/action/{11_nested_shortcuts_demo.html → 11_nested_shortcuts_demo.xhtml} +0 -0
  137. /package/src/components/{shortcut → keyboard_shortcuts}/os.js +0 -0
  138. /package/src/route/{route.test.html → route.xtest.html} +0 -0
@@ -0,0 +1,313 @@
1
+ /**
2
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Constraint_validation
3
+ */
4
+
5
+ // this constraint is not really a native constraint and browser just not let this happen at all
6
+ // in our case it's just here in case some code is wrongly calling "requestAction" or "checkValidity" on a disabled element
7
+ export const DISABLED_CONSTRAINT = {
8
+ name: "disabled",
9
+ check: (element) => {
10
+ if (element.disabled) {
11
+ return `Ce champ est désactivé.`;
12
+ }
13
+ return null;
14
+ },
15
+ };
16
+
17
+ export const REQUIRED_CONSTRAINT = {
18
+ name: "required",
19
+ check: (element, { registerChange }) => {
20
+ if (!element.required) {
21
+ return null;
22
+ }
23
+ const requiredMessage = element.getAttribute("data-required-message");
24
+
25
+ if (element.type === "checkbox") {
26
+ if (!element.checked) {
27
+ return requiredMessage || `Veuillez cocher cette case.`;
28
+ }
29
+ return null;
30
+ }
31
+ if (element.type === "radio") {
32
+ // For radio buttons, check if any radio with the same name is selected
33
+ const name = element.name;
34
+ if (!name) {
35
+ // If no name, check just this radio
36
+ if (!element.checked) {
37
+ return requiredMessage || `Veuillez sélectionner une option.`;
38
+ }
39
+ return null;
40
+ }
41
+
42
+ const closestFieldset = element.closest("fieldset");
43
+ const fieldsetRequiredMessage = closestFieldset
44
+ ? closestFieldset.getAttribute("data-required-message")
45
+ : null;
46
+
47
+ // Find the container (form or closest fieldset)
48
+ const container = element.form || closestFieldset || document;
49
+ // Check if any radio with the same name is checked
50
+ const radioSelector = `input[type="radio"][name="${CSS.escape(name)}"]`;
51
+ const radiosWithSameName = container.querySelectorAll(radioSelector);
52
+ for (const radio of radiosWithSameName) {
53
+ if (radio.checked) {
54
+ return null; // At least one radio is selected
55
+ }
56
+ registerChange((onChange) => {
57
+ radio.addEventListener("change", onChange);
58
+ return () => {
59
+ radio.removeEventListener("change", onChange);
60
+ };
61
+ });
62
+ }
63
+
64
+ return {
65
+ message:
66
+ requiredMessage ||
67
+ fieldsetRequiredMessage ||
68
+ `Veuillez sélectionner une option.`,
69
+ target: closestFieldset
70
+ ? closestFieldset.querySelector("legend")
71
+ : undefined,
72
+ };
73
+ }
74
+ if (!element.value) {
75
+ return requiredMessage || `Veuillez remplir ce champ.`;
76
+ }
77
+ return null;
78
+ },
79
+ };
80
+ export const PATTERN_CONSTRAINT = {
81
+ name: "pattern",
82
+ check: (input) => {
83
+ const pattern = input.pattern;
84
+ if (!pattern) {
85
+ return null;
86
+ }
87
+ const value = input.value;
88
+ if (!value) {
89
+ return null;
90
+ }
91
+ const regex = new RegExp(pattern);
92
+ if (!regex.test(value)) {
93
+ const patternMessage = input.getAttribute("data-pattern-message");
94
+ if (patternMessage) {
95
+ return patternMessage;
96
+ }
97
+ let message = `Veuillez respecter le format requis.`;
98
+ const title = input.title;
99
+ if (title) {
100
+ message += `<br />${title}`;
101
+ }
102
+ return message;
103
+ }
104
+ return null;
105
+ },
106
+ };
107
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/email#validation
108
+ const emailregex =
109
+ /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
110
+ export const TYPE_EMAIL_CONSTRAINT = {
111
+ name: "type_email",
112
+ check: (input) => {
113
+ if (input.type !== "email") {
114
+ return null;
115
+ }
116
+ const value = input.value;
117
+ if (!value) {
118
+ return null;
119
+ }
120
+ if (!value.includes("@")) {
121
+ return `Veuillez inclure "@" dans l'adresse e-mail. Il manque un symbole "@" dans ${value}.`;
122
+ }
123
+ if (!emailregex.test(value)) {
124
+ return `Veuillez saisir une adresse e-mail valide.`;
125
+ }
126
+ return null;
127
+ },
128
+ };
129
+
130
+ export const MIN_LENGTH_CONSTRAINT = {
131
+ name: "min_length",
132
+ check: (element) => {
133
+ if (element.tagName === "INPUT") {
134
+ if (!INPUT_TYPE_SUPPORTING_MIN_LENGTH_SET.has(element.type)) {
135
+ return null;
136
+ }
137
+ } else if (element.tagName !== "TEXTAREA") {
138
+ return null;
139
+ }
140
+
141
+ const minLength = element.minLength;
142
+ if (minLength === -1) {
143
+ return null;
144
+ }
145
+
146
+ const value = element.value;
147
+ const valueLength = value.length;
148
+ if (valueLength === 0) {
149
+ return null;
150
+ }
151
+ if (valueLength < minLength) {
152
+ if (valueLength === 1) {
153
+ return `Ce champ doit contenir au moins ${minLength} caractère (il contient actuellement un seul caractère).`;
154
+ }
155
+ return `Ce champ doit contenir au moins ${minLength} caractères (il contient actuellement ${valueLength} caractères).`;
156
+ }
157
+ return null;
158
+ },
159
+ };
160
+ const INPUT_TYPE_SUPPORTING_MIN_LENGTH_SET = new Set([
161
+ "text",
162
+ "search",
163
+ "url",
164
+ "tel",
165
+ "email",
166
+ "password",
167
+ ]);
168
+
169
+ export const MAX_LENGTH_CONSTRAINT = {
170
+ name: "max_length",
171
+ check: (element) => {
172
+ if (element.tagName === "INPUT") {
173
+ if (!INPUT_TYPE_SUPPORTING_MAX_LENGTH_SET.has(element.type)) {
174
+ return null;
175
+ }
176
+ } else if (element.tagName !== "TEXTAREA") {
177
+ return null;
178
+ }
179
+
180
+ const maxLength = element.maxLength;
181
+ if (maxLength === -1) {
182
+ return null;
183
+ }
184
+
185
+ const value = element.value;
186
+ const valueLength = value.length;
187
+ if (valueLength > maxLength) {
188
+ return `Ce champ doit contenir au maximum ${maxLength} caractères (il contient actuellement ${valueLength} caractères).`;
189
+ }
190
+ return null;
191
+ },
192
+ };
193
+ const INPUT_TYPE_SUPPORTING_MAX_LENGTH_SET = new Set(
194
+ INPUT_TYPE_SUPPORTING_MIN_LENGTH_SET,
195
+ );
196
+
197
+ export const TYPE_NUMBER_CONSTRAINT = {
198
+ name: "type_number",
199
+ check: (element) => {
200
+ if (element.tagName !== "INPUT") {
201
+ return null;
202
+ }
203
+ if (element.type !== "number") {
204
+ return null;
205
+ }
206
+ if (element.value === "") {
207
+ return null;
208
+ }
209
+ const value = element.valueAsNumber;
210
+ if (isNaN(value)) {
211
+ return `Doit être un nombre.`;
212
+ }
213
+ return null;
214
+ },
215
+ };
216
+
217
+ export const MIN_CONSTRAINT = {
218
+ name: "min",
219
+ check: (element) => {
220
+ if (element.tagName !== "INPUT") {
221
+ return null;
222
+ }
223
+ if (element.type === "number") {
224
+ const minString = element.min;
225
+ if (minString === "") {
226
+ return null;
227
+ }
228
+ const minNumber = parseFloat(minString);
229
+ if (isNaN(minNumber)) {
230
+ return null;
231
+ }
232
+ const valueAsNumber = element.valueAsNumber;
233
+ if (isNaN(valueAsNumber)) {
234
+ return null;
235
+ }
236
+ if (valueAsNumber < minNumber) {
237
+ const minMessage = element.getAttribute("data-min-message");
238
+ return (
239
+ minMessage ||
240
+ `Doit être supérieur ou égal à <strong>${minString}</strong>.`
241
+ );
242
+ }
243
+ return null;
244
+ }
245
+ if (element.type === "time") {
246
+ const min = element.min;
247
+ if (min === undefined) {
248
+ return null;
249
+ }
250
+ const [minHours, minMinutes] = min.split(":").map(Number);
251
+ const value = element.value;
252
+ const [hours, minutes] = value.split(":").map(Number);
253
+ if (hours < minHours) {
254
+ return `Doit être <strong>${min}</strong> ou plus.`;
255
+ }
256
+ if (hours === minHours && minMinutes < minutes) {
257
+ return `Doit être <strong>${min}</strong> ou plus.`;
258
+ }
259
+ return null;
260
+ }
261
+ // "range"
262
+ // - user interface do not let user enter anything outside the boundaries
263
+ // - when setting value via js browser enforce boundaries too
264
+ // "date", "month", "week", "datetime-local"
265
+ // - same as "range"
266
+ return null;
267
+ },
268
+ };
269
+
270
+ export const MAX_CONSTRAINT = {
271
+ name: "max",
272
+ check: (element) => {
273
+ if (element.tagName !== "INPUT") {
274
+ return null;
275
+ }
276
+ if (element.type === "number") {
277
+ const maxString = element.max;
278
+ if (maxString === "") {
279
+ return null;
280
+ }
281
+ const maxNumber = parseFloat(maxString);
282
+ if (isNaN(maxNumber)) {
283
+ return null;
284
+ }
285
+ const valueAsNumber = element.valueAsNumber;
286
+ if (isNaN(valueAsNumber)) {
287
+ return null;
288
+ }
289
+ if (valueAsNumber > maxNumber) {
290
+ const maxMessage = element.getAttribute("data-max-message");
291
+ return maxMessage || `Doit être <strong>${maxString}</strong> ou plus.`;
292
+ }
293
+ return null;
294
+ }
295
+ if (element.type === "time") {
296
+ const max = element.min;
297
+ if (max === undefined) {
298
+ return null;
299
+ }
300
+ const [maxHours, maxMinutes] = max.split(":").map(Number);
301
+ const value = element.value;
302
+ const [hours, minutes] = value.split(":").map(Number);
303
+ if (hours > maxHours) {
304
+ return `Doit être <strong>${max}</strong> ou moins.`;
305
+ }
306
+ if (hours === maxHours && maxMinutes > minutes) {
307
+ return `Doit être <strong>${max}</strong> ou moins.`;
308
+ }
309
+ return null;
310
+ }
311
+ return null;
312
+ },
313
+ };
@@ -0,0 +1,36 @@
1
+ export const READONLY_CONSTRAINT = {
2
+ name: "readonly",
3
+ check: (element, { skipReadonly }) => {
4
+ if (skipReadonly) {
5
+ return null;
6
+ }
7
+ if (!element.readonly && !element.hasAttribute("data-readonly")) {
8
+ return null;
9
+ }
10
+ const readonlySilent = element.hasAttribute("data-readonly-silent");
11
+ if (readonlySilent) {
12
+ return { silent: true };
13
+ }
14
+ const readonlyMessage = element.getAttribute("data-readonly-message");
15
+ if (readonlyMessage) {
16
+ return {
17
+ message: readonlyMessage,
18
+ level: "info",
19
+ };
20
+ }
21
+ const isBusy = element.getAttribute("aria-busy") === "true";
22
+ if (isBusy) {
23
+ return {
24
+ message: `Cette action est en cours. Veuillez patienter.`,
25
+ level: "info",
26
+ };
27
+ }
28
+ return {
29
+ message:
30
+ element.tagName === "BUTTON"
31
+ ? `Cet action n'est pas disponible pour l'instant.`
32
+ : `Cet élément est en lecture seule et ne peut pas être modifié.`,
33
+ level: "info",
34
+ };
35
+ },
36
+ };
@@ -0,0 +1,13 @@
1
+ export const SINGLE_SPACE_CONSTRAINT = {
2
+ name: "single_space",
3
+ check: (input) => {
4
+ const inputValue = input.value;
5
+ const hasLeadingSpace = inputValue.startsWith(" ");
6
+ const hasTrailingSpace = inputValue.endsWith(" ");
7
+ const hasDoubleSpace = inputValue.includes(" ");
8
+ if (hasLeadingSpace || hasDoubleSpace || hasTrailingSpace) {
9
+ return "Spaces at the beginning, end, or consecutive spaces are not allowed";
10
+ }
11
+ return "";
12
+ },
13
+ };