@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
@@ -54,7 +54,12 @@ export const updateRoutes = (
54
54
  const newActive = Boolean(match);
55
55
  let newParams;
56
56
  if (match) {
57
- const extractedParams = extractParams(urlPattern, url);
57
+ const { optionalParamKeySet } = routePrivateProperties;
58
+ const extractedParams = extractParams(
59
+ urlPattern,
60
+ url,
61
+ optionalParamKeySet,
62
+ );
58
63
  if (compareTwoJsValues(oldParams, extractedParams)) {
59
64
  // No change in parameters, keep the old params
60
65
  newParams = oldParams;
@@ -112,6 +117,43 @@ export const updateRoutes = (
112
117
  const abortSignalMap = new Map();
113
118
  const routeLoadRequestedMap = new Map();
114
119
 
120
+ const shouldLoadOrReload = (route, shouldLoad) => {
121
+ const routeAction = route.action;
122
+ const currentAction = routeAction.getCurrentAction();
123
+ if (shouldLoad) {
124
+ if (replace || currentAction.aborted || currentAction.error) {
125
+ shouldLoad = false;
126
+ }
127
+ }
128
+ if (shouldLoad) {
129
+ toLoadSet.add(currentAction);
130
+ } else {
131
+ toReloadSet.add(currentAction);
132
+ }
133
+ routeLoadRequestedMap.set(route, currentAction);
134
+ // Create a new abort controller for this action
135
+ const actionAbortController = new AbortController();
136
+ actionAbortControllerWeakMap.set(currentAction, actionAbortController);
137
+ abortSignalMap.set(currentAction, actionAbortController.signal);
138
+ };
139
+
140
+ const shouldLoad = (route) => {
141
+ shouldLoadOrReload(route, true);
142
+ };
143
+ const shouldReload = (route) => {
144
+ shouldLoadOrReload(route, false);
145
+ };
146
+ const shouldAbort = (route) => {
147
+ const routeAction = route.action;
148
+ const currentAction = routeAction.getCurrentAction();
149
+ const actionAbortController =
150
+ actionAbortControllerWeakMap.get(currentAction);
151
+ if (actionAbortController) {
152
+ actionAbortController.abort(`route no longer matching`);
153
+ actionAbortControllerWeakMap.delete(currentAction);
154
+ }
155
+ };
156
+
115
157
  for (const {
116
158
  route,
117
159
  routePrivateProperties,
@@ -138,31 +180,13 @@ export const updateRoutes = (
138
180
  newParams,
139
181
  );
140
182
  }
141
- const currentAction = routeAction.getCurrentAction();
142
- if (replace) {
143
- toLoadSet.add(currentAction);
144
- } else {
145
- toReloadSet.add(currentAction);
146
- }
147
- routeLoadRequestedMap.set(route, currentAction);
148
-
149
- // Create a new abort controller for this action
150
- const actionAbortController = new AbortController();
151
- actionAbortControllerWeakMap.set(currentAction, actionAbortController);
152
- abortSignalMap.set(currentAction, actionAbortController.signal);
153
-
183
+ shouldLoad(route);
154
184
  continue;
155
185
  }
156
186
 
157
187
  // Handle actions for routes that become inactive - abort them
158
188
  if (becomesInactive && ROUTE_DEACTIVATION_STRATEGY === "abort") {
159
- const currentAction = routeAction.getCurrentAction();
160
- const actionAbortController =
161
- actionAbortControllerWeakMap.get(currentAction);
162
- if (actionAbortController) {
163
- actionAbortController.abort(`route no longer matching`);
164
- actionAbortControllerWeakMap.delete(currentAction);
165
- }
189
+ shouldAbort(route);
166
190
  continue;
167
191
  }
168
192
 
@@ -174,16 +198,7 @@ export const updateRoutes = (
174
198
  newParams,
175
199
  );
176
200
  }
177
- const currentAction = routeAction.getCurrentAction();
178
- if (!replace || currentAction.aborted || currentAction.error) {
179
- toReloadSet.add(currentAction);
180
- routeLoadRequestedMap.set(route, currentAction);
181
- // Create a new abort controller for the reload
182
- const actionAbortController = new AbortController();
183
- actionAbortControllerWeakMap.set(currentAction, actionAbortController);
184
- abortSignalMap.set(currentAction, actionAbortController.signal);
185
- }
186
- continue;
201
+ shouldReload(route);
187
202
  }
188
203
  }
189
204
 
@@ -195,7 +210,7 @@ export const updateRoutes = (
195
210
  activeRouteSet,
196
211
  };
197
212
  };
198
- const extractParams = (urlPattern, url) => {
213
+ const extractParams = (urlPattern, url, ignoreSet = new Set()) => {
199
214
  const match = urlPattern.exec(url);
200
215
  if (!match) {
201
216
  return NO_PARAMS;
@@ -213,12 +228,15 @@ const extractParams = (urlPattern, url) => {
213
228
  const keyAsNumber = parseInt(key, 10);
214
229
  if (!isNaN(keyAsNumber)) {
215
230
  if (value) {
216
- // Only include non-empty values
217
- params[wildcardOffset + keyAsNumber] = decodeURIComponent(value);
231
+ // Only include non-empty values and non-ignored wildcard indices
232
+ const wildcardKey = String(wildcardOffset + keyAsNumber);
233
+ if (!ignoreSet.has(wildcardKey)) {
234
+ params[wildcardKey] = decodeURIComponent(value);
235
+ }
218
236
  localWildcardCount++;
219
237
  }
220
- } else {
221
- // Named group (:param or {param})
238
+ } else if (!ignoreSet.has(key)) {
239
+ // Named group (:param or {param}) - only include if not ignored
222
240
  params[key] = decodeURIComponent(value);
223
241
  }
224
242
  }
@@ -252,6 +270,7 @@ const createRoute = (urlPatternInput) => {
252
270
  };
253
271
 
254
272
  const route = {
273
+ urlPattern: urlPatternInput,
255
274
  isRoute: true,
256
275
  active: false,
257
276
  params: NO_PARAMS,
@@ -275,11 +294,13 @@ const createRoute = (urlPatternInput) => {
275
294
  visitedSignal: null,
276
295
  relativeUrlSignal: null,
277
296
  urlSignal: null,
297
+ optionalParamKeySet: null,
278
298
  };
279
299
  routePrivatePropertiesMap.set(route, routePrivateProperties);
280
300
 
281
301
  const buildRelativeUrl = (params = {}) => {
282
302
  let relativeUrl = urlPatternInput;
303
+
283
304
  // Replace named parameters (:param and {param})
284
305
  for (const key of Object.keys(params)) {
285
306
  const value = params[key];
@@ -287,16 +308,25 @@ const createRoute = (urlPatternInput) => {
287
308
  relativeUrl = relativeUrl.replace(`:${key}`, encodedValue);
288
309
  relativeUrl = relativeUrl.replace(`{${key}}`, encodedValue);
289
310
  }
290
- // Replace wildcards (*) with numbered parameters (0, 1, 2, etc.)
291
- let wildcardIndex = 0;
292
- relativeUrl = relativeUrl.replace(/\*/g, () => {
293
- const paramKey = wildcardIndex.toString();
294
- const replacement = params[paramKey]
295
- ? encodeURIComponent(params[paramKey])
296
- : "*";
297
- wildcardIndex++;
298
- return replacement;
299
- });
311
+
312
+ // Handle wildcards: if the pattern ends with /*? (optional wildcard)
313
+ // always remove the wildcard part for URL building since it's optional
314
+ if (relativeUrl.endsWith("/*?")) {
315
+ // Always remove the optional wildcard part for URL building
316
+ relativeUrl = relativeUrl.replace(/\/\*\?$/, "");
317
+ } else {
318
+ // For required wildcards (/*) or other patterns, replace normally
319
+ let wildcardIndex = 0;
320
+ relativeUrl = relativeUrl.replace(/\*/g, () => {
321
+ const paramKey = wildcardIndex.toString();
322
+ const replacement = params[paramKey]
323
+ ? encodeURIComponent(params[paramKey])
324
+ : "*";
325
+ wildcardIndex++;
326
+ return replacement;
327
+ });
328
+ }
329
+
300
330
  return relativeUrl;
301
331
  };
302
332
  const buildUrl = (params = {}) => {
@@ -429,6 +459,23 @@ const createRoute = (urlPatternInput) => {
429
459
  routePrivateProperties.relativeUrlSignal = relativeUrlSignal;
430
460
  routePrivateProperties.urlSignal = urlSignal;
431
461
  routePrivateProperties.cleanupCallbackSet = cleanupCallbackSet;
462
+
463
+ // Analyze pattern once to detect optional params (named and wildcard indices)
464
+ // Note: Wildcard indices are stored as strings ("0", "1", ...) to match keys from extractParams
465
+ const optionalParamKeySet = new Set();
466
+ normalizedUrlPattern.replace(/:([A-Za-z0-9_]+)\?/g, (_m, name) => {
467
+ optionalParamKeySet.add(name);
468
+ return "";
469
+ });
470
+ let wildcardIndex = 0;
471
+ normalizedUrlPattern.replace(/\*(\?)?/g, (_m, opt) => {
472
+ if (opt === "?") {
473
+ optionalParamKeySet.add(String(wildcardIndex));
474
+ }
475
+ wildcardIndex++;
476
+ return "";
477
+ });
478
+ routePrivateProperties.optionalParamKeySet = optionalParamKeySet;
432
479
  }
433
480
 
434
481
  return route;
@@ -450,13 +497,16 @@ export const useRouteStatus = (route) => {
450
497
  throw new Error(`Cannot find route private properties for ${route}`);
451
498
  }
452
499
 
453
- const { activeSignal, paramsSignal, visitedSignal } = routePrivateProperties;
500
+ const { urlSignal, activeSignal, paramsSignal, visitedSignal } =
501
+ routePrivateProperties;
454
502
 
503
+ const url = urlSignal.value;
455
504
  const active = activeSignal.value;
456
505
  const params = paramsSignal.value;
457
506
  const visited = visitedSignal.value;
458
507
 
459
508
  return {
509
+ url,
460
510
  active,
461
511
  params,
462
512
  visited,
@@ -1,4 +1,6 @@
1
+ import { createIterableWeakSet } from "@jsenv/dom";
1
2
  import { computed, signal } from "@preact/signals";
3
+
2
4
  import { getActionPrivateProperties } from "../action_private_properties.js";
3
5
  import { createAction, getActionDispatcher } from "../actions.js";
4
6
  import { SYMBOL_OBJECT_SIGNAL } from "../symbol_object_signal.js";
@@ -7,7 +9,6 @@ import {
7
9
  compareTwoJsValues,
8
10
  } from "../utils/compare_two_js_values.js";
9
11
  import { getCallerInfo } from "../utils/get_caller_info.js";
10
- import { createIterableWeakSet } from "../utils/iterable_weak_set.js";
11
12
  import { arraySignalStore, primitiveCanBeId } from "./array_signal_store.js";
12
13
 
13
14
  let DEBUG = false;
@@ -51,45 +51,45 @@ console.log(
51
51
  );
52
52
 
53
53
  // Test the autoreload mechanism
54
- async function testAutoreload() {
55
- console.log("\n🧪 Testing autoreload mechanism...");
54
+ async function testAutorerun() {
55
+ console.log("\n🧪 Testing autorerun mechanism...");
56
56
 
57
57
  try {
58
58
  // Load the parameterized resource
59
- const ownershipData = await ROLE_WITH_OWNERSHIP.GET_MANY.load();
59
+ const ownershipData = await ROLE_WITH_OWNERSHIP.GET_MANY.run();
60
60
  console.log(
61
- "✅ ROLE_WITH_OWNERSHIP.GET_MANY.load() executed:",
61
+ "✅ ROLE_WITH_OWNERSHIP.GET_MANY.run() executed:",
62
62
  ownershipData.length,
63
63
  "items",
64
64
  );
65
65
 
66
- // Trigger dependency changes that should cause autoreload
66
+ // Trigger dependency changes that should cause autorerun
67
67
  console.log("\n📝 Triggering dependency changes...");
68
68
 
69
- await tables.POST.load({ name: "new_table", database_id: 1 });
69
+ await tables.POST.run({ name: "new_table", database_id: 1 });
70
70
  console.log(
71
- "✅ tables.POST.load() - should trigger ROLE_WITH_OWNERSHIP autoreload",
71
+ "✅ tables.POST.run() - should trigger ROLE_WITH_OWNERSHIP autorerun",
72
72
  );
73
73
 
74
- await database.POST.load({ name: "new_database" });
74
+ await database.POST.run({ name: "new_database" });
75
75
  console.log(
76
- "✅ database.POST.load() - should trigger ROLE_WITH_OWNERSHIP autoreload",
76
+ "✅ database.POST.run() - should trigger ROLE_WITH_OWNERSHIP autorerun",
77
77
  );
78
78
 
79
- await role.POST.load({ name: "new_role" });
79
+ await role.POST.run({ name: "new_role" });
80
80
  console.log(
81
- "✅ role.POST.load() - should trigger ROLE_WITH_OWNERSHIP autoreload",
81
+ "✅ role.POST.run() - should trigger ROLE_WITH_OWNERSHIP autorerun",
82
82
  );
83
83
 
84
84
  console.log("\n✅ Dependency system test completed successfully!");
85
85
  console.log(
86
- "💡 In a real application, these would automatically reload ROLE_WITH_OWNERSHIP.GET_MANY",
86
+ "💡 In a real application, these would automatically rerun ROLE_WITH_OWNERSHIP.GET_MANY",
87
87
  );
88
88
  } catch (error) {
89
89
  console.error("❌ Test failed:", error);
90
90
  }
91
91
  }
92
92
 
93
- testAutoreload();
93
+ testAutorerun();
94
94
 
95
95
  export { database, role, ROLE_WITH_OWNERSHIP, tables };
@@ -0,0 +1,20 @@
1
+ export const isSignal = (value) => {
2
+ return getSignalType(value) !== null;
3
+ };
4
+
5
+ const BRAND_SYMBOL = Symbol.for("preact-signals");
6
+ export const getSignalType = (value) => {
7
+ if (!value || typeof value !== "object") {
8
+ return null;
9
+ }
10
+
11
+ if (value.brand !== BRAND_SYMBOL) {
12
+ return null;
13
+ }
14
+
15
+ if (typeof value._fn === "function") {
16
+ return "computed";
17
+ }
18
+
19
+ return "signal";
20
+ };
@@ -1,3 +1,7 @@
1
+ import { getSignalType } from "./is_signal.js";
2
+
3
+ const MAX_ENTRIES = 5;
4
+
1
5
  export const stringifyForDisplay = (
2
6
  value,
3
7
  maxDepth = 2,
@@ -125,26 +129,3 @@ export const stringifyForDisplay = (
125
129
 
126
130
  return String(value);
127
131
  };
128
-
129
- export const isSignal = (value) => {
130
- return getSignalType(value) !== null;
131
- };
132
-
133
- const BRAND_SYMBOL = Symbol.for("preact-signals");
134
- export const getSignalType = (value) => {
135
- if (!value || typeof value !== "object") {
136
- return null;
137
- }
138
-
139
- if (value.brand !== BRAND_SYMBOL) {
140
- return null;
141
- }
142
-
143
- if (typeof value._fn === "function") {
144
- return "computed";
145
- }
146
-
147
- return "signal";
148
- };
149
-
150
- const MAX_ENTRIES = 5;
@@ -0,0 +1,14 @@
1
+ export const CONFIRM_CONSTRAINT = {
2
+ name: "confirm",
3
+ check: (element) => {
4
+ const confirmMessage = element.getAttribute("data-confirm");
5
+ if (!confirmMessage) {
6
+ return "";
7
+ }
8
+ // eslint-disable-next-line no-alert
9
+ if (window.confirm(confirmMessage)) {
10
+ return "";
11
+ }
12
+ return "";
13
+ },
14
+ };
@@ -0,0 +1,27 @@
1
+ export const createUniqueValueConstraint = (
2
+ // the set might be incomplete (the front usually don't have the full copy of all the items from the backend)
3
+ // but this is already nice to help user with what we know
4
+ // it's also possible that front is unsync with backend, preventing user to choose a value
5
+ // that is actually free.
6
+ // But this is unlikely to happen and user could reload the page to be able to choose that name
7
+ // that suddenly became available
8
+ existingValueSet,
9
+ message = `"{value}" already exists. Please choose another value.`,
10
+ ) => {
11
+ return {
12
+ name: "unique_value",
13
+ check: (input) => {
14
+ const inputValue = input.value;
15
+ const hasConflict = existingValueSet.has(inputValue);
16
+ // console.log({
17
+ // inputValue,
18
+ // names: Array.from(otherNameSet.values()),
19
+ // hasConflict,
20
+ // });
21
+ if (hasConflict) {
22
+ return message.replace("{value}", inputValue);
23
+ }
24
+ return "";
25
+ },
26
+ };
27
+ };