@jsenv/navi 0.0.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 (123) hide show
  1. package/index.js +51 -0
  2. package/package.json +38 -0
  3. package/src/action_private_properties.js +11 -0
  4. package/src/action_proxy_test.html +353 -0
  5. package/src/action_run_states.js +5 -0
  6. package/src/actions.js +1377 -0
  7. package/src/browser_integration/browser_integration.js +191 -0
  8. package/src/browser_integration/document_back_and_forward.js +17 -0
  9. package/src/browser_integration/document_loading_signal.js +100 -0
  10. package/src/browser_integration/document_state_signal.js +9 -0
  11. package/src/browser_integration/document_url_signal.js +9 -0
  12. package/src/browser_integration/use_is_visited.js +19 -0
  13. package/src/browser_integration/via_history.js +199 -0
  14. package/src/browser_integration/via_navigation.js +168 -0
  15. package/src/components/action_execution/form_context.js +8 -0
  16. package/src/components/action_execution/render_actionable_component.jsx +27 -0
  17. package/src/components/action_execution/use_action.js +330 -0
  18. package/src/components/action_execution/use_execute_action.js +161 -0
  19. package/src/components/action_renderer.jsx +136 -0
  20. package/src/components/collect_form_element_values.js +79 -0
  21. package/src/components/demos/0_button_demo.html +155 -0
  22. package/src/components/demos/1_checkbox_demo.html +257 -0
  23. package/src/components/demos/2_input_textual_demo.html +354 -0
  24. package/src/components/demos/3_radio_demo.html +222 -0
  25. package/src/components/demos/4_select_demo.html +104 -0
  26. package/src/components/demos/5_list_scrollable_demo.html +153 -0
  27. package/src/components/demos/action/0_button_demo.html +204 -0
  28. package/src/components/demos/action/10_shortcuts_demo.html +189 -0
  29. package/src/components/demos/action/11_nested_shortcuts_demo.html +401 -0
  30. package/src/components/demos/action/1_input_text_demo.html +461 -0
  31. package/src/components/demos/action/2_form_multiple.html +303 -0
  32. package/src/components/demos/action/3_details_demo.html +172 -0
  33. package/src/components/demos/action/4_input_checkbox_demo.html +611 -0
  34. package/src/components/demos/action/6_checkbox_list_demo.html +109 -0
  35. package/src/components/demos/action/7_radio_list_demo.html +217 -0
  36. package/src/components/demos/action/8_editable_text_demo.html +442 -0
  37. package/src/components/demos/action/9_link_demo.html +172 -0
  38. package/src/components/demos/demo.md +0 -0
  39. package/src/components/demos/route/basic/basic.html +14 -0
  40. package/src/components/demos/route/basic/basic_route_demo.jsx +224 -0
  41. package/src/components/demos/route/multi/multi.html +14 -0
  42. package/src/components/demos/route/multi/multi_route_demo.jsx +277 -0
  43. package/src/components/details/details.jsx +248 -0
  44. package/src/components/details/summary_marker.jsx +141 -0
  45. package/src/components/editable_text/editable_text.jsx +96 -0
  46. package/src/components/error_boundary_context.js +9 -0
  47. package/src/components/form.jsx +144 -0
  48. package/src/components/input/button.jsx +333 -0
  49. package/src/components/input/checkbox_list.jsx +294 -0
  50. package/src/components/input/field.jsx +61 -0
  51. package/src/components/input/field_css.js +118 -0
  52. package/src/components/input/input.jsx +15 -0
  53. package/src/components/input/input_checkbox.jsx +370 -0
  54. package/src/components/input/input_radio.jsx +299 -0
  55. package/src/components/input/input_textual.jsx +338 -0
  56. package/src/components/input/radio_list.jsx +283 -0
  57. package/src/components/input/select.jsx +273 -0
  58. package/src/components/input/use_form_event.js +20 -0
  59. package/src/components/input/use_on_change.js +12 -0
  60. package/src/components/link/link.jsx +291 -0
  61. package/src/components/loader/loader_background.jsx +324 -0
  62. package/src/components/loader/loading_spinner.jsx +68 -0
  63. package/src/components/loader/network_speed.js +83 -0
  64. package/src/components/loader/rectangle_loading.jsx +225 -0
  65. package/src/components/route.jsx +15 -0
  66. package/src/components/selection/selection.js +5 -0
  67. package/src/components/selection/selection_context.jsx +262 -0
  68. package/src/components/shortcut/os.js +9 -0
  69. package/src/components/shortcut/shortcut_context.jsx +390 -0
  70. package/src/components/use_action_events.js +37 -0
  71. package/src/components/use_auto_focus.js +43 -0
  72. package/src/components/use_debounce_true.js +31 -0
  73. package/src/components/use_focus_group.js +19 -0
  74. package/src/components/use_initial_value.js +104 -0
  75. package/src/components/use_is_visited.js +19 -0
  76. package/src/components/use_ref_array.js +38 -0
  77. package/src/components/use_signal_sync.js +50 -0
  78. package/src/components/use_state_array.js +40 -0
  79. package/src/docs/actions.md +228 -0
  80. package/src/docs/demos/resource/action_status.jsx +42 -0
  81. package/src/docs/demos/resource/demo.md +1 -0
  82. package/src/docs/demos/resource/resource_demo_0.html +84 -0
  83. package/src/docs/demos/resource/resource_demo_10_post_gc.html +364 -0
  84. package/src/docs/demos/resource/resource_demo_11_describe_many.html +362 -0
  85. package/src/docs/demos/resource/resource_demo_2.html +173 -0
  86. package/src/docs/demos/resource/resource_demo_3_filtered_users.html +415 -0
  87. package/src/docs/demos/resource/resource_demo_4_details.html +284 -0
  88. package/src/docs/demos/resource/resource_demo_5_renderer_lazy.html +115 -0
  89. package/src/docs/demos/resource/resource_demo_6_gc.html +217 -0
  90. package/src/docs/demos/resource/resource_demo_7_child_gc.html +240 -0
  91. package/src/docs/demos/resource/resource_demo_8_proxy_gc.html +319 -0
  92. package/src/docs/demos/resource/resource_demo_9_describe_one.html +472 -0
  93. package/src/docs/demos/resource/tata.jsx +3 -0
  94. package/src/docs/demos/resource/toto.jsx +3 -0
  95. package/src/docs/demos/user_nav/user_nav.html +12 -0
  96. package/src/docs/demos/user_nav/user_nav.jsx +330 -0
  97. package/src/docs/resource_dependencies.md +103 -0
  98. package/src/docs/resource_with_params.md +80 -0
  99. package/src/notes.md +13 -0
  100. package/src/route/route.js +518 -0
  101. package/src/route/route.test.html +228 -0
  102. package/src/store/array_signal_store.js +537 -0
  103. package/src/store/local_storage_signal.js +17 -0
  104. package/src/store/resource_graph.js +1303 -0
  105. package/src/store/tests/resource_graph_autoreload_demo.html +12 -0
  106. package/src/store/tests/resource_graph_autoreload_demo.jsx +964 -0
  107. package/src/store/tests/resource_graph_dependencies.test.js +95 -0
  108. package/src/store/value_in_local_storage.js +187 -0
  109. package/src/symbol_object_signal.js +1 -0
  110. package/src/use_action_data.js +10 -0
  111. package/src/use_action_status.js +47 -0
  112. package/src/utils/add_many_event_listeners.js +15 -0
  113. package/src/utils/array_add_remove.js +61 -0
  114. package/src/utils/array_signal.js +15 -0
  115. package/src/utils/compare_two_js_values.js +172 -0
  116. package/src/utils/execute_with_cleanup.js +21 -0
  117. package/src/utils/get_caller_info.js +85 -0
  118. package/src/utils/iterable_weak_set.js +62 -0
  119. package/src/utils/js_value_weak_map.js +162 -0
  120. package/src/utils/js_value_weak_map_demo.html +690 -0
  121. package/src/utils/merge_two_js_values.js +53 -0
  122. package/src/utils/stringify_for_display.js +150 -0
  123. package/src/utils/weak_effect.js +48 -0
@@ -0,0 +1,442 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" href="data:," />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Editable Text Demo</title>
8
+ <style>
9
+ body {
10
+ font-family:
11
+ -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
12
+ max-width: 1400px;
13
+ margin: 0 auto;
14
+ padding: 20px;
15
+ background-color: #f8f9fa;
16
+ }
17
+
18
+ h1 {
19
+ text-align: center;
20
+ color: #333;
21
+ margin-bottom: 30px;
22
+ }
23
+
24
+ .demo-grid {
25
+ display: grid;
26
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
27
+ gap: 20px;
28
+ }
29
+
30
+ .demo-card {
31
+ background: white;
32
+ border-radius: 8px;
33
+ padding: 20px;
34
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
35
+ border: 1px solid #e1e8ed;
36
+ }
37
+
38
+ .demo-title {
39
+ color: #2c3e50;
40
+ margin-top: 0;
41
+ margin-bottom: 15px;
42
+ font-size: 18px;
43
+ font-weight: 600;
44
+ border-bottom: 2px solid #3498db;
45
+ padding-bottom: 8px;
46
+ }
47
+
48
+ .control-group {
49
+ margin-bottom: 15px;
50
+ }
51
+
52
+ label {
53
+ display: block;
54
+ margin-bottom: 5px;
55
+ font-weight: 500;
56
+ color: #555;
57
+ }
58
+
59
+ .button-group {
60
+ display: flex;
61
+ gap: 8px;
62
+ margin-top: 12px;
63
+ flex-wrap: wrap;
64
+ }
65
+
66
+ button {
67
+ padding: 6px 12px;
68
+ border: 1px solid #3498db;
69
+ background: #3498db;
70
+ color: white;
71
+ border-radius: 4px;
72
+ cursor: pointer;
73
+ font-size: 14px;
74
+ transition: all 0.2s ease;
75
+ box-sizing: border-box;
76
+ }
77
+
78
+ button:hover {
79
+ background: #2980b9;
80
+ border-color: #2980b9;
81
+ }
82
+
83
+ button:disabled {
84
+ background: #95a5a6;
85
+ border-color: #95a5a6;
86
+ cursor: not-allowed;
87
+ }
88
+
89
+ .editable-container {
90
+ margin: 15px 0;
91
+ padding: 15px;
92
+ background: #f8f9fa;
93
+ border: 1px solid #dee2e6;
94
+ border-radius: 6px;
95
+ min-height: 40px;
96
+ font-size: 16px;
97
+ line-height: 1.4;
98
+ }
99
+
100
+ .result-display {
101
+ background: #f8f9fa;
102
+ border: 1px solid #dee2e6;
103
+ border-radius: 4px;
104
+ padding: 12px;
105
+ margin-top: 15px;
106
+ font-family:
107
+ "SF Mono", "Monaco", "Inconsolata", "Fira Code", "Fira Mono",
108
+ "Droid Sans Mono", "Courier New", monospace;
109
+ font-size: 13px;
110
+ color: #495057;
111
+ min-height: 20px;
112
+ white-space: pre-wrap;
113
+ }
114
+
115
+ .result-success {
116
+ background: #d4edda;
117
+ border-color: #c3e6cb;
118
+ color: #155724;
119
+ }
120
+
121
+ .result-error {
122
+ background: #f8d7da;
123
+ border-color: #f5c6cb;
124
+ color: #721c24;
125
+ }
126
+
127
+ .result-loading {
128
+ background: #d1ecf1;
129
+ border-color: #bee5eb;
130
+ color: #0c5460;
131
+ animation: pulse 1.5s ease-in-out infinite;
132
+ }
133
+
134
+ @keyframes pulse {
135
+ 0%,
136
+ 100% {
137
+ opacity: 1;
138
+ }
139
+ 50% {
140
+ opacity: 0.7;
141
+ }
142
+ }
143
+
144
+ @media (max-width: 768px) {
145
+ .demo-grid {
146
+ grid-template-columns: 1fr;
147
+ }
148
+ }
149
+ </style>
150
+ </head>
151
+ <body>
152
+ <h1>Editable Text Demo</h1>
153
+ <div id="root"></div>
154
+
155
+ <script type="module" jsenv-type="module/jsx">
156
+ import { render } from "preact";
157
+ import { useState } from "preact/hooks";
158
+ import { signal } from "@preact/signals";
159
+ import { SINGLE_SPACE_CONSTRAINT } from "@jsenv/validation";
160
+ import {
161
+ useEditableController,
162
+ // eslint-disable-next-line no-unused-vars
163
+ EditableText,
164
+ createAction,
165
+ } from "@jsenv/navi";
166
+
167
+ // eslint-disable-next-line no-unused-vars
168
+ const App = () => {
169
+ return (
170
+ <div className="demo-grid">
171
+ <BasicDemo />
172
+ <LoadingDemo />
173
+ <ErrorDemo />
174
+ <CustomStyleDemo />
175
+ <SignalControlDemo />
176
+ </div>
177
+ );
178
+ };
179
+
180
+ // eslint-disable-next-line no-unused-vars
181
+ const BasicDemo = () => {
182
+ const { editable, startEditing, stopEditing } = useEditableController();
183
+ const [value, setValue] = useState("Click edit to change this text");
184
+ const [result, setResult] = useState("");
185
+
186
+ return (
187
+ <div className="demo-card">
188
+ <h3 className="demo-title">Basic Editable Text</h3>
189
+ <div className="control-group">
190
+ <label>Editable Content:</label>
191
+ <div className="editable-container">
192
+ <EditableText
193
+ editable={editable}
194
+ onEditEnd={() => {
195
+ stopEditing();
196
+ setResult(``);
197
+ }}
198
+ name="test"
199
+ action={({ test }) => {
200
+ setValue(test);
201
+ return `Updated to: "${test}"`;
202
+ }}
203
+ value={value}
204
+ constraints={[SINGLE_SPACE_CONSTRAINT]}
205
+ />
206
+ </div>
207
+ </div>
208
+ <div className="button-group">
209
+ <button onClick={() => startEditing()} disabled={editable}>
210
+ {editable ? "Editing..." : "Start Editing"}
211
+ </button>
212
+ </div>
213
+ <div
214
+ className={`result-display ${result.startsWith("✅") ? "result-success" : result.startsWith("❌") ? "result-error" : ""}`}
215
+ >
216
+ Current value: "{value}"
217
+ <br />
218
+ {result || "Click 'Start Editing' to modify the text..."}
219
+ </div>
220
+ </div>
221
+ );
222
+ };
223
+
224
+ // eslint-disable-next-line no-unused-vars
225
+ const LoadingDemo = () => {
226
+ const { editable, startEditing, stopEditing } = useEditableController();
227
+ const [value, setValue] = useState("This will take 2 seconds to save");
228
+ const [result, setResult] = useState("");
229
+ const [isLoading, setIsLoading] = useState(false);
230
+
231
+ return (
232
+ <div className="demo-card">
233
+ <h3 className="demo-title">Editable Text with Loading</h3>
234
+ <div className="control-group">
235
+ <label>Editable Content (2s delay):</label>
236
+ <div className="editable-container">
237
+ <EditableText
238
+ editable={editable}
239
+ onEditEnd={() => {
240
+ stopEditing();
241
+ setIsLoading(false);
242
+ setResult();
243
+ }}
244
+ name="test"
245
+ action={async ({ test }) => {
246
+ setIsLoading(true);
247
+ setResult("Saving...");
248
+ await new Promise((resolve) => setTimeout(resolve, 2000));
249
+ setValue(test);
250
+ return `Saved: "${test}"`;
251
+ }}
252
+ value={value}
253
+ constraints={[SINGLE_SPACE_CONSTRAINT]}
254
+ />
255
+ </div>
256
+ </div>
257
+ <div className="button-group">
258
+ <button
259
+ onClick={() => startEditing()}
260
+ disabled={editable || isLoading}
261
+ >
262
+ {isLoading
263
+ ? "Saving..."
264
+ : editable
265
+ ? "Editing..."
266
+ : "Start Editing"}
267
+ </button>
268
+ </div>
269
+ <div
270
+ className={`result-display ${isLoading ? "result-loading" : result.startsWith("✅") ? "result-success" : result.startsWith("❌") ? "result-error" : ""}`}
271
+ >
272
+ Current value: "{value}"
273
+ <br />
274
+ {result || "This demo simulates a 2-second save operation..."}
275
+ </div>
276
+ </div>
277
+ );
278
+ };
279
+
280
+ // eslint-disable-next-line no-unused-vars
281
+ const ErrorDemo = () => {
282
+ const { editable, startEditing, stopEditing } = useEditableController();
283
+ const [value] = useState("This edit will always fail");
284
+ const [result, setResult] = useState("");
285
+
286
+ return (
287
+ <div className="demo-card">
288
+ <h3 className="demo-title">Editable Text with Error Handling</h3>
289
+ <div className="control-group">
290
+ <label>Editable Content (always fails):</label>
291
+ <div className="editable-container">
292
+ <EditableText
293
+ editable={editable}
294
+ onEditEnd={() => {
295
+ stopEditing();
296
+ setResult("");
297
+ }}
298
+ name="test"
299
+ action={async ({ test }) => {
300
+ setResult("Processing...");
301
+ await new Promise((resolve) => setTimeout(resolve, 500));
302
+ throw new Error(`Cannot save "${test}" - simulated error`);
303
+ }}
304
+ value={value}
305
+ constraints={[SINGLE_SPACE_CONSTRAINT]}
306
+ />
307
+ </div>
308
+ </div>
309
+ <div className="button-group">
310
+ <button onClick={() => startEditing()} disabled={editable}>
311
+ {editable ? "Editing..." : "Start Editing (Will Fail)"}
312
+ </button>
313
+ </div>
314
+ <div
315
+ className={`result-display ${result === "Processing..." ? "result-loading" : result.startsWith("✅") ? "result-success" : result.startsWith("❌") ? "result-error" : ""}`}
316
+ >
317
+ Current value: "{value}"
318
+ <br />
319
+ {result || "This demo always throws an error when saving..."}
320
+ </div>
321
+ </div>
322
+ );
323
+ };
324
+
325
+ // eslint-disable-next-line no-unused-vars
326
+ const CustomStyleDemo = () => {
327
+ const { editable, startEditing, stopEditing } = useEditableController();
328
+ const [value, setValue] = useState("Bold styled text");
329
+ const [result, setResult] = useState("");
330
+
331
+ return (
332
+ <div className="demo-card">
333
+ <h3 className="demo-title">Editable Text with Custom Styling</h3>
334
+ <div className="control-group">
335
+ <label>Styled Editable Content:</label>
336
+ <div className="editable-container">
337
+ <EditableText
338
+ editable={editable}
339
+ onEditEnd={() => {
340
+ stopEditing();
341
+ setResult("");
342
+ }}
343
+ name="test"
344
+ action={({ test }) => {
345
+ setValue(test);
346
+ return `Updated styled text: "${test}"`;
347
+ }}
348
+ value={value}
349
+ constraints={[SINGLE_SPACE_CONSTRAINT]}
350
+ >
351
+ <strong style={{ color: "#e74c3c", fontSize: "18px" }}>
352
+ {value}
353
+ </strong>
354
+ </EditableText>
355
+ </div>
356
+ </div>
357
+ <div className="button-group">
358
+ <button onClick={() => startEditing()} disabled={editable}>
359
+ {editable ? "Editing..." : "Start Editing"}
360
+ </button>
361
+ </div>
362
+ <div
363
+ className={`result-display ${result.startsWith("✅") ? "result-success" : result.startsWith("❌") ? "result-error" : ""}`}
364
+ >
365
+ Current value: "{value}"
366
+ <br />
367
+ {result || "This demo shows custom styling with bold red text..."}
368
+ </div>
369
+ </div>
370
+ );
371
+ };
372
+
373
+ // Create signal outside component to persist across renders
374
+ const valueSignal = signal("Signal-controlled editable text");
375
+ const action = createAction(async ({ test }) => {
376
+ await new Promise((resolve) => setTimeout(resolve, 800));
377
+ return `Signal update: "${test}"`;
378
+ });
379
+ const boundAction = action.bindParams({ test: valueSignal });
380
+
381
+ // eslint-disable-next-line no-unused-vars
382
+ const SignalControlDemo = () => {
383
+ const [result, setResult] = useState("");
384
+ const { editable, startEditing, stopEditing } = useEditableController();
385
+
386
+ return (
387
+ <div className="demo-card">
388
+ <h3 className="demo-title">Signal-Controlled Editable Text</h3>
389
+ <div className="control-group">
390
+ <label>Signal-Controlled Content:</label>
391
+ <div className="editable-container">
392
+ <EditableText
393
+ valueSignal={valueSignal}
394
+ editable={editable}
395
+ onEditEnd={() => {
396
+ stopEditing();
397
+ setResult("");
398
+ }}
399
+ onActionStart={() => {
400
+ setResult("Saving via signal...");
401
+ }}
402
+ name="coucou"
403
+ action={boundAction}
404
+ cancelOnEscape
405
+ cancelOnBlurInvalid
406
+ constraints={[SINGLE_SPACE_CONSTRAINT]}
407
+ >
408
+ <span
409
+ onClick={() => {
410
+ startEditing();
411
+ }}
412
+ >
413
+ {valueSignal.value}
414
+ </span>
415
+ </EditableText>
416
+ </div>
417
+ </div>
418
+ <div className="button-group">
419
+ <button
420
+ onClick={() => {
421
+ console.log("Setting editable signal to true");
422
+ valueSignal.value = "toto";
423
+ }}
424
+ >
425
+ Set signal value to "toto"
426
+ </button>
427
+ </div>
428
+ <div
429
+ className={`result-display ${result === "Saving via signal..." ? "result-loading" : result.startsWith("✅") ? "result-success" : result.startsWith("❌") ? "result-error" : ""}`}
430
+ >
431
+ Signal value: {valueSignal.value}
432
+ <br />
433
+ {result || "Use buttons to control edit mode via signal..."}
434
+ </div>
435
+ </div>
436
+ );
437
+ };
438
+
439
+ render(<App />, document.querySelector("#root"));
440
+ </script>
441
+ </body>
442
+ </html>
@@ -0,0 +1,172 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" href="data:," />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Link demo</title>
8
+ </head>
9
+ <body>
10
+ <div id="root" style="position: relative"></div>
11
+
12
+ <script type="module" jsenv-type="module/jsx">
13
+ import { render } from "preact";
14
+ import { useState } from "preact/hooks";
15
+ // eslint-disable-next-line no-unused-vars
16
+ import { Link, SelectionProvider, defineRoutes } from "@jsenv/navi";
17
+
18
+ defineRoutes({});
19
+
20
+ // eslint-disable-next-line no-unused-vars
21
+ const App = () => {
22
+ const [contextSelectedItems, setContextSelectedItems] = useState([]);
23
+
24
+ return (
25
+ <div style="padding: 20px; font-family: system-ui, sans-serif; max-width: 800px;">
26
+ <h1>Link Component Demo</h1>
27
+
28
+ <section style="margin-bottom: 40px;">
29
+ <h2>Basic Links</h2>
30
+ <div style="display: flex; flex-direction: column; gap: 12px;">
31
+ <Link href="#default">Default link</Link>
32
+
33
+ <Link href="#active" active>
34
+ Active link
35
+ </Link>
36
+
37
+ <Link href="#visited" visited>
38
+ Visited link
39
+ </Link>
40
+
41
+ <Link href="#readonly" readOnly>
42
+ Read-only link
43
+ </Link>
44
+
45
+ <Link href="#disabled" disabled>
46
+ Disabled link
47
+ </Link>
48
+
49
+ <Link href="#loading" loading readOnly>
50
+ Loading link
51
+ </Link>
52
+ </div>
53
+ </section>
54
+
55
+ <section style="margin-bottom: 40px;">
56
+ <h2>Styled Links</h2>
57
+ <div style="border: 1px solid #e0e0e0; border-radius: 8px; padding: 16px; background: #f8f9fa;">
58
+ <div style="display: flex; flex-direction: column; gap: 12px;">
59
+ <Link
60
+ href="#"
61
+ style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px 20px; border-radius: 6px; text-decoration: none; font-weight: 500; display: inline-block; transition: transform 0.2s;"
62
+ >
63
+ Gradient button link
64
+ </Link>
65
+
66
+ <Link
67
+ href="#"
68
+ style="background: #28a745; color: white; padding: 12px 20px; border-radius: 6px; text-decoration: none; font-weight: 500; display: inline-block; transition: transform 0.2s;"
69
+ >
70
+ Success button link
71
+ </Link>
72
+
73
+ <Link
74
+ href="#"
75
+ style="background: #dc3545; color: white; padding: 12px 20px; border-radius: 6px; text-decoration: none; font-weight: 500; display: inline-block; transition: transform 0.2s;"
76
+ data-outline-inset
77
+ loading
78
+ >
79
+ Loading button link
80
+ </Link>
81
+ </div>
82
+ </div>
83
+ </section>
84
+
85
+ <section style="margin-bottom: 40px;">
86
+ <h2>Selectable Links</h2>
87
+ <p style="color: #666; margin-bottom: 16px;">
88
+ Using Selection context for centralized state management.{" "}
89
+ <kbd style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 0.9em;">
90
+ Cmd/Ctrl + Click
91
+ </kbd>{" "}
92
+ to multi-select,{" "}
93
+ <kbd style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 0.9em;">
94
+ Shift + Click
95
+ </kbd>{" "}
96
+ for range selection, and{" "}
97
+ <kbd style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 0.9em;">
98
+ Cmd/Ctrl + Delete
99
+ </kbd>{" "}
100
+ to show selected items.
101
+ </p>
102
+
103
+ <SelectionProvider
104
+ value={contextSelectedItems}
105
+ onChange={setContextSelectedItems}
106
+ >
107
+ <div style="border: 1px solid #e0e0e0; border-radius: 8px; padding: 16px; background: white;">
108
+ <div style="display: flex; flex-direction: column; gap: 8px;">
109
+ <Link href="#doc_report" name="doc" value="doc-1">
110
+ 📋 Annual Report 2024
111
+ </Link>
112
+
113
+ <Link href="#doc_budget" name="doc" value="doc-2">
114
+ 💰 Budget Proposal
115
+ </Link>
116
+
117
+ <Link href="#doc_presentation" name="doc" value="doc-3">
118
+ 🎯 Marketing Presentation
119
+ </Link>
120
+
121
+ <Link href="#doc_manual" name="doc" value="doc-4">
122
+ 📖 User Manual v2.0
123
+ </Link>
124
+ </div>
125
+
126
+ {contextSelectedItems.length > 0 && (
127
+ <div style="margin-top: 16px; padding: 12px; background: #e3f2fd; border-radius: 6px; border-left: 4px solid #1976d2;">
128
+ <strong>Selected items:</strong>{" "}
129
+ {contextSelectedItems.join(", ")}
130
+ <br />
131
+ <button
132
+ onClick={() => setContextSelectedItems([])}
133
+ style="margin-top: 8px; padding: 4px 12px; background: #1976d2; color: white; border: none; border-radius: 4px; font-size: 0.9em; cursor: pointer;"
134
+ >
135
+ Clear selection
136
+ </button>
137
+ </div>
138
+ )}
139
+ </div>
140
+ </SelectionProvider>
141
+ </section>
142
+
143
+ <section>
144
+ <h2>Navigation Instructions</h2>
145
+ <div style="background: #f8f9fa; padding: 16px; border-radius: 6px; color: #666;">
146
+ <ul style="margin: 0; padding-left: 20px;">
147
+ <li>
148
+ <strong>Normal click:</strong> Navigate to the link
149
+ </li>
150
+ <li>
151
+ <strong>Cmd/Ctrl + click:</strong> Toggle selection (for
152
+ selectable links)
153
+ </li>
154
+ <li>
155
+ <strong>Shift + click:</strong> Range selection (for
156
+ selectable links)
157
+ </li>
158
+ <li>
159
+ <strong>Cmd/Ctrl + Delete/Backspace:</strong> Show alert
160
+ with selected items (when focused on a selectable link)
161
+ </li>
162
+ </ul>
163
+ </div>
164
+ </section>
165
+ </div>
166
+ );
167
+ };
168
+
169
+ render(<App />, document.querySelector("#root"));
170
+ </script>
171
+ </body>
172
+ </html>
File without changes
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" href="data:," />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Routing demo</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+
12
+ <script type="module" src="./basic_route_demo.jsx"></script>
13
+ </body>
14
+ </html>