@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.
- package/index.js +51 -0
- package/package.json +38 -0
- package/src/action_private_properties.js +11 -0
- package/src/action_proxy_test.html +353 -0
- package/src/action_run_states.js +5 -0
- package/src/actions.js +1377 -0
- package/src/browser_integration/browser_integration.js +191 -0
- package/src/browser_integration/document_back_and_forward.js +17 -0
- package/src/browser_integration/document_loading_signal.js +100 -0
- package/src/browser_integration/document_state_signal.js +9 -0
- package/src/browser_integration/document_url_signal.js +9 -0
- package/src/browser_integration/use_is_visited.js +19 -0
- package/src/browser_integration/via_history.js +199 -0
- package/src/browser_integration/via_navigation.js +168 -0
- package/src/components/action_execution/form_context.js +8 -0
- package/src/components/action_execution/render_actionable_component.jsx +27 -0
- package/src/components/action_execution/use_action.js +330 -0
- package/src/components/action_execution/use_execute_action.js +161 -0
- package/src/components/action_renderer.jsx +136 -0
- package/src/components/collect_form_element_values.js +79 -0
- package/src/components/demos/0_button_demo.html +155 -0
- package/src/components/demos/1_checkbox_demo.html +257 -0
- package/src/components/demos/2_input_textual_demo.html +354 -0
- package/src/components/demos/3_radio_demo.html +222 -0
- package/src/components/demos/4_select_demo.html +104 -0
- package/src/components/demos/5_list_scrollable_demo.html +153 -0
- package/src/components/demos/action/0_button_demo.html +204 -0
- package/src/components/demos/action/10_shortcuts_demo.html +189 -0
- package/src/components/demos/action/11_nested_shortcuts_demo.html +401 -0
- package/src/components/demos/action/1_input_text_demo.html +461 -0
- package/src/components/demos/action/2_form_multiple.html +303 -0
- package/src/components/demos/action/3_details_demo.html +172 -0
- package/src/components/demos/action/4_input_checkbox_demo.html +611 -0
- package/src/components/demos/action/6_checkbox_list_demo.html +109 -0
- package/src/components/demos/action/7_radio_list_demo.html +217 -0
- package/src/components/demos/action/8_editable_text_demo.html +442 -0
- package/src/components/demos/action/9_link_demo.html +172 -0
- package/src/components/demos/demo.md +0 -0
- package/src/components/demos/route/basic/basic.html +14 -0
- package/src/components/demos/route/basic/basic_route_demo.jsx +224 -0
- package/src/components/demos/route/multi/multi.html +14 -0
- package/src/components/demos/route/multi/multi_route_demo.jsx +277 -0
- package/src/components/details/details.jsx +248 -0
- package/src/components/details/summary_marker.jsx +141 -0
- package/src/components/editable_text/editable_text.jsx +96 -0
- package/src/components/error_boundary_context.js +9 -0
- package/src/components/form.jsx +144 -0
- package/src/components/input/button.jsx +333 -0
- package/src/components/input/checkbox_list.jsx +294 -0
- package/src/components/input/field.jsx +61 -0
- package/src/components/input/field_css.js +118 -0
- package/src/components/input/input.jsx +15 -0
- package/src/components/input/input_checkbox.jsx +370 -0
- package/src/components/input/input_radio.jsx +299 -0
- package/src/components/input/input_textual.jsx +338 -0
- package/src/components/input/radio_list.jsx +283 -0
- package/src/components/input/select.jsx +273 -0
- package/src/components/input/use_form_event.js +20 -0
- package/src/components/input/use_on_change.js +12 -0
- package/src/components/link/link.jsx +291 -0
- package/src/components/loader/loader_background.jsx +324 -0
- package/src/components/loader/loading_spinner.jsx +68 -0
- package/src/components/loader/network_speed.js +83 -0
- package/src/components/loader/rectangle_loading.jsx +225 -0
- package/src/components/route.jsx +15 -0
- package/src/components/selection/selection.js +5 -0
- package/src/components/selection/selection_context.jsx +262 -0
- package/src/components/shortcut/os.js +9 -0
- package/src/components/shortcut/shortcut_context.jsx +390 -0
- package/src/components/use_action_events.js +37 -0
- package/src/components/use_auto_focus.js +43 -0
- package/src/components/use_debounce_true.js +31 -0
- package/src/components/use_focus_group.js +19 -0
- package/src/components/use_initial_value.js +104 -0
- package/src/components/use_is_visited.js +19 -0
- package/src/components/use_ref_array.js +38 -0
- package/src/components/use_signal_sync.js +50 -0
- package/src/components/use_state_array.js +40 -0
- package/src/docs/actions.md +228 -0
- package/src/docs/demos/resource/action_status.jsx +42 -0
- package/src/docs/demos/resource/demo.md +1 -0
- package/src/docs/demos/resource/resource_demo_0.html +84 -0
- package/src/docs/demos/resource/resource_demo_10_post_gc.html +364 -0
- package/src/docs/demos/resource/resource_demo_11_describe_many.html +362 -0
- package/src/docs/demos/resource/resource_demo_2.html +173 -0
- package/src/docs/demos/resource/resource_demo_3_filtered_users.html +415 -0
- package/src/docs/demos/resource/resource_demo_4_details.html +284 -0
- package/src/docs/demos/resource/resource_demo_5_renderer_lazy.html +115 -0
- package/src/docs/demos/resource/resource_demo_6_gc.html +217 -0
- package/src/docs/demos/resource/resource_demo_7_child_gc.html +240 -0
- package/src/docs/demos/resource/resource_demo_8_proxy_gc.html +319 -0
- package/src/docs/demos/resource/resource_demo_9_describe_one.html +472 -0
- package/src/docs/demos/resource/tata.jsx +3 -0
- package/src/docs/demos/resource/toto.jsx +3 -0
- package/src/docs/demos/user_nav/user_nav.html +12 -0
- package/src/docs/demos/user_nav/user_nav.jsx +330 -0
- package/src/docs/resource_dependencies.md +103 -0
- package/src/docs/resource_with_params.md +80 -0
- package/src/notes.md +13 -0
- package/src/route/route.js +518 -0
- package/src/route/route.test.html +228 -0
- package/src/store/array_signal_store.js +537 -0
- package/src/store/local_storage_signal.js +17 -0
- package/src/store/resource_graph.js +1303 -0
- package/src/store/tests/resource_graph_autoreload_demo.html +12 -0
- package/src/store/tests/resource_graph_autoreload_demo.jsx +964 -0
- package/src/store/tests/resource_graph_dependencies.test.js +95 -0
- package/src/store/value_in_local_storage.js +187 -0
- package/src/symbol_object_signal.js +1 -0
- package/src/use_action_data.js +10 -0
- package/src/use_action_status.js +47 -0
- package/src/utils/add_many_event_listeners.js +15 -0
- package/src/utils/array_add_remove.js +61 -0
- package/src/utils/array_signal.js +15 -0
- package/src/utils/compare_two_js_values.js +172 -0
- package/src/utils/execute_with_cleanup.js +21 -0
- package/src/utils/get_caller_info.js +85 -0
- package/src/utils/iterable_weak_set.js +62 -0
- package/src/utils/js_value_weak_map.js +162 -0
- package/src/utils/js_value_weak_map_demo.html +690 -0
- package/src/utils/merge_two_js_values.js +53 -0
- package/src/utils/stringify_for_display.js +150 -0
- 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>
|