jsgui3-server 0.0.144 → 0.0.145

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 (47) hide show
  1. package/docs/jsgui3-html-improvement-ideas.md +162 -0
  2. package/docs/jsgui3-html-improvement-ideas.svg +151 -0
  3. package/examples/controls/14d) window, canvas globe/EarthGlobeRenderer.js +19 -14
  4. package/examples/controls/14d) window, canvas globe/pipeline/TransformStage.js +5 -5
  5. package/examples/jsgui3-html/01) mvvm-counter/client.js +648 -0
  6. package/examples/jsgui3-html/01) mvvm-counter/server.js +21 -0
  7. package/examples/jsgui3-html/02) date-transform/client.js +764 -0
  8. package/examples/jsgui3-html/02) date-transform/server.js +21 -0
  9. package/examples/jsgui3-html/03) form-validation/client.js +1045 -0
  10. package/examples/jsgui3-html/03) form-validation/server.js +21 -0
  11. package/examples/jsgui3-html/04) data-grid/client.js +738 -0
  12. package/examples/jsgui3-html/04) data-grid/server.js +21 -0
  13. package/examples/jsgui3-html/05) master-detail/client.js +649 -0
  14. package/examples/jsgui3-html/05) master-detail/server.js +21 -0
  15. package/examples/jsgui3-html/06) theming/client.js +514 -0
  16. package/examples/jsgui3-html/06) theming/server.js +21 -0
  17. package/examples/jsgui3-html/07) mixins/client.js +465 -0
  18. package/examples/jsgui3-html/07) mixins/server.js +21 -0
  19. package/examples/jsgui3-html/08) router/client.js +372 -0
  20. package/examples/jsgui3-html/08) router/server.js +21 -0
  21. package/examples/jsgui3-html/09) resource-transform/client.js +692 -0
  22. package/examples/jsgui3-html/09) resource-transform/server.js +21 -0
  23. package/examples/jsgui3-html/10) binding-debugger/client.js +810 -0
  24. package/examples/jsgui3-html/10) binding-debugger/server.js +21 -0
  25. package/examples/jsgui3-html/README.md +48 -0
  26. package/http/responders/static/Static_Route_HTTP_Responder.js +25 -20
  27. package/package.json +3 -3
  28. package/publishers/http-webpageorsite-publisher.js +3 -1
  29. package/serve-factory.js +12 -5
  30. package/server.js +103 -85
  31. package/tests/README.md +7 -0
  32. package/tests/end-to-end.test.js +336 -365
  33. package/tests/examples-controls.e2e.test.js +13 -1
  34. package/tests/fixtures/end-to-end-client.js +54 -0
  35. package/tests/fixtures/jsgui3-html/binding_debugger_expectations.json +15 -0
  36. package/tests/fixtures/jsgui3-html/counter_expectations.json +31 -0
  37. package/tests/fixtures/jsgui3-html/data_grid_expectations.json +26 -0
  38. package/tests/fixtures/jsgui3-html/date_transform_expectations.json +26 -0
  39. package/tests/fixtures/jsgui3-html/form_validation_expectations.json +27 -0
  40. package/tests/fixtures/jsgui3-html/master_detail_expectations.json +15 -0
  41. package/tests/fixtures/jsgui3-html/mixins_expectations.json +10 -0
  42. package/tests/fixtures/jsgui3-html/resource_transform_expectations.json +11 -0
  43. package/tests/fixtures/jsgui3-html/router_expectations.json +10 -0
  44. package/tests/fixtures/jsgui3-html/theming_expectations.json +10 -0
  45. package/tests/jsgui3-html-examples.puppeteer.test.js +537 -0
  46. package/tests/test-runner.js +1 -0
  47. package/tests/window-examples.puppeteer.test.js +217 -1
@@ -0,0 +1,372 @@
1
+ const jsgui = require('jsgui3-client');
2
+ const Active_HTML_Document = require('../../../controls/Active_HTML_Document');
3
+ const { Data_Object } = jsgui;
4
+
5
+ const ROUTE_MAP = Object.freeze({
6
+ overview: {
7
+ title: 'Overview',
8
+ body: 'Start here to see route switching in action.',
9
+ hint: 'Hash route: #overview'
10
+ },
11
+ metrics: {
12
+ title: 'Metrics',
13
+ body: 'Track momentum, throughput, and focus signals.',
14
+ hint: 'Hash route: #metrics'
15
+ },
16
+ settings: {
17
+ title: 'Settings',
18
+ body: 'Tune controls and preferences for the workspace.',
19
+ hint: 'Hash route: #settings'
20
+ }
21
+ });
22
+
23
+ class Router_Control extends jsgui.Control {
24
+ constructor(spec = {}) {
25
+ spec.__type_name = spec.__type_name || 'router_control';
26
+ super(spec);
27
+
28
+ this.data.model = new Data_Object({
29
+ route_name: 'overview'
30
+ });
31
+
32
+ this.view.data.model = new Data_Object({
33
+ route_title: '',
34
+ route_body: '',
35
+ route_hint: ''
36
+ });
37
+
38
+ this.setup_watchers();
39
+ this.update_view_for_route(this.data.model.route_name);
40
+
41
+ if (!spec.el) {
42
+ this.compose();
43
+ }
44
+ }
45
+
46
+ setup_watchers() {
47
+ this.watch(
48
+ this.data.model,
49
+ 'route_name',
50
+ (route_name) => {
51
+ this.update_view_for_route(route_name);
52
+ }
53
+ );
54
+ }
55
+
56
+ get_route_config(route_name) {
57
+ return ROUTE_MAP[route_name] || ROUTE_MAP.overview;
58
+ }
59
+
60
+ set_route(route_name, options = {}) {
61
+ const target_route = ROUTE_MAP[route_name] ? route_name : 'overview';
62
+ this.data.model.route_name = target_route;
63
+
64
+ if (options.update_hash && typeof window !== 'undefined') {
65
+ window.location.hash = target_route;
66
+ }
67
+ }
68
+
69
+ update_view_for_route(route_name) {
70
+ const route_config = this.get_route_config(route_name);
71
+ this.view.data.model.route_title = route_config.title;
72
+ this.view.data.model.route_body = route_config.body;
73
+ this.view.data.model.route_hint = route_config.hint;
74
+ }
75
+
76
+ update_nav_active(nav_buttons, active_route) {
77
+ nav_buttons.forEach((button_el) => {
78
+ const button_route = button_el.getAttribute('data-route');
79
+ const is_active = button_route === active_route;
80
+ button_el.classList.toggle('is-active', is_active);
81
+ button_el.setAttribute('aria-current', is_active ? 'page' : 'false');
82
+ });
83
+ }
84
+
85
+ activate() {
86
+ if (this.__active) return;
87
+ super.activate();
88
+
89
+ if (this._dom_bound) return;
90
+ const root_el = this.dom.el;
91
+ if (!root_el) return;
92
+ this._dom_bound = true;
93
+
94
+ const title_el = root_el.querySelector('[data-test="route-title"]');
95
+ const body_el = root_el.querySelector('[data-test="route-body"]');
96
+ const hint_el = root_el.querySelector('[data-test="route-hint"]');
97
+ const nav_buttons = Array.from(root_el.querySelectorAll('[data-route]'));
98
+
99
+ nav_buttons.forEach((button_el) => {
100
+ button_el.addEventListener('click', (event) => {
101
+ event.preventDefault();
102
+ const route_name = button_el.getAttribute('data-route');
103
+ this.set_route(route_name, { update_hash: true });
104
+ });
105
+ });
106
+
107
+ if (typeof window !== 'undefined') {
108
+ const apply_hash_route = () => {
109
+ const raw_hash = window.location.hash || '';
110
+ const route_name = raw_hash.replace('#', '').trim();
111
+ if (ROUTE_MAP[route_name]) {
112
+ this.set_route(route_name, { update_hash: false });
113
+ }
114
+ };
115
+ apply_hash_route();
116
+ window.addEventListener('hashchange', apply_hash_route);
117
+ }
118
+
119
+ this.watch(
120
+ this.view.data.model,
121
+ 'route_title',
122
+ (route_title) => {
123
+ if (title_el) title_el.textContent = route_title || '';
124
+ },
125
+ { immediate: true }
126
+ );
127
+
128
+ this.watch(
129
+ this.view.data.model,
130
+ 'route_body',
131
+ (route_body) => {
132
+ if (body_el) body_el.textContent = route_body || '';
133
+ },
134
+ { immediate: true }
135
+ );
136
+
137
+ this.watch(
138
+ this.view.data.model,
139
+ 'route_hint',
140
+ (route_hint) => {
141
+ if (hint_el) hint_el.textContent = route_hint || '';
142
+ },
143
+ { immediate: true }
144
+ );
145
+
146
+ this.watch(
147
+ this.data.model,
148
+ 'route_name',
149
+ (route_name) => {
150
+ this.update_nav_active(nav_buttons, route_name);
151
+ },
152
+ { immediate: true }
153
+ );
154
+ }
155
+
156
+ compose() {
157
+ // Framework expects the method name `compose`.
158
+ const page_context = this.context;
159
+
160
+ this.add_class('router-control');
161
+ this.dom.attributes['data-test'] = 'router-control';
162
+
163
+ const header = new jsgui.Control({
164
+ context: page_context,
165
+ tagName: 'header',
166
+ class: 'router-header'
167
+ });
168
+
169
+ header.add(new jsgui.Control({
170
+ context: page_context,
171
+ tagName: 'h1',
172
+ class: 'router-title',
173
+ content: 'Route Switcher'
174
+ }));
175
+ header.add(new jsgui.Control({
176
+ context: page_context,
177
+ tagName: 'p',
178
+ class: 'router-subtitle',
179
+ content: 'Navigate between routes and sync with the hash.'
180
+ }));
181
+
182
+ const nav = new jsgui.Control({
183
+ context: page_context,
184
+ tagName: 'nav',
185
+ class: 'router-nav'
186
+ });
187
+
188
+ const make_nav_button = (label_text, route_name) => {
189
+ const button = new jsgui.Control({
190
+ context: page_context,
191
+ tagName: 'a',
192
+ class: 'router-link',
193
+ content: label_text
194
+ });
195
+ button.dom.attributes.href = `#${route_name}`;
196
+ button.dom.attributes['data-route'] = route_name;
197
+ button.dom.attributes['data-test'] = `nav-${route_name}`;
198
+ return button;
199
+ };
200
+
201
+ nav.add(make_nav_button('Overview', 'overview'));
202
+ nav.add(make_nav_button('Metrics', 'metrics'));
203
+ nav.add(make_nav_button('Settings', 'settings'));
204
+
205
+ const route_panel = new jsgui.Control({
206
+ context: page_context,
207
+ tagName: 'section',
208
+ class: 'router-panel'
209
+ });
210
+
211
+ const route_title = new jsgui.Control({
212
+ context: page_context,
213
+ tagName: 'h2',
214
+ class: 'route-title',
215
+ content: this.view.data.model.route_title
216
+ });
217
+ route_title.dom.attributes['data-test'] = 'route-title';
218
+
219
+ const route_body = new jsgui.Control({
220
+ context: page_context,
221
+ tagName: 'p',
222
+ class: 'route-body',
223
+ content: this.view.data.model.route_body
224
+ });
225
+ route_body.dom.attributes['data-test'] = 'route-body';
226
+
227
+ const route_hint = new jsgui.Control({
228
+ context: page_context,
229
+ tagName: 'div',
230
+ class: 'route-hint',
231
+ content: this.view.data.model.route_hint
232
+ });
233
+ route_hint.dom.attributes['data-test'] = 'route-hint';
234
+
235
+ route_panel.add(route_title);
236
+ route_panel.add(route_body);
237
+ route_panel.add(route_hint);
238
+
239
+ this.add(header);
240
+ this.add(nav);
241
+ this.add(route_panel);
242
+ }
243
+ }
244
+
245
+ class Demo_UI extends Active_HTML_Document {
246
+ constructor(spec = {}) {
247
+ spec.__type_name = spec.__type_name || 'router_demo_ui';
248
+ super(spec);
249
+
250
+ if (!spec.el) {
251
+ this.compose();
252
+ }
253
+ }
254
+
255
+ compose() {
256
+ // Framework expects the method name `compose`.
257
+ const page_context = this.context;
258
+ this.body.add_class('router-demo');
259
+
260
+ const router_control = new Router_Control({
261
+ context: page_context
262
+ });
263
+
264
+ this.body.add(router_control);
265
+ }
266
+ }
267
+
268
+ Demo_UI.css = `
269
+ :root {
270
+ --rt-ink: #16161d;
271
+ --rt-muted: #5b5966;
272
+ --rt-accent: #41749b;
273
+ --rt-panel: #ffffff;
274
+ --rt-border: #d7d9e2;
275
+ --rt-bg: #eef1f6;
276
+ }
277
+
278
+ * {
279
+ box-sizing: border-box;
280
+ }
281
+
282
+ body {
283
+ margin: 0;
284
+ padding: 0;
285
+ background: linear-gradient(140deg, #e9eef5 0%, #f2f4f8 50%, #ece8df 100%);
286
+ color: var(--rt-ink);
287
+ font-family: "Manrope", "Arial", sans-serif;
288
+ }
289
+
290
+ .router-demo {
291
+ min-height: 100vh;
292
+ display: flex;
293
+ align-items: center;
294
+ justify-content: center;
295
+ padding: 40px 24px;
296
+ }
297
+
298
+ .router-control {
299
+ width: min(860px, 100%);
300
+ background: var(--rt-panel);
301
+ border-radius: 24px;
302
+ padding: 32px 36px 40px;
303
+ border: 1px solid var(--rt-border);
304
+ box-shadow: 0 24px 60px rgba(20, 23, 35, 0.16);
305
+ }
306
+
307
+ .router-header {
308
+ margin-bottom: 18px;
309
+ }
310
+
311
+ .router-title {
312
+ margin: 0 0 8px;
313
+ font-size: 30px;
314
+ }
315
+
316
+ .router-subtitle {
317
+ margin: 0;
318
+ color: var(--rt-muted);
319
+ font-size: 15px;
320
+ }
321
+
322
+ .router-nav {
323
+ display: flex;
324
+ gap: 12px;
325
+ margin: 20px 0;
326
+ flex-wrap: wrap;
327
+ }
328
+
329
+ .router-link {
330
+ text-decoration: none;
331
+ color: var(--rt-ink);
332
+ border: 1px solid #d2d6e0;
333
+ padding: 8px 14px;
334
+ border-radius: 999px;
335
+ font-size: 13px;
336
+ }
337
+
338
+ .router-link.is-active {
339
+ background: var(--rt-accent);
340
+ border-color: var(--rt-accent);
341
+ color: #ffffff;
342
+ }
343
+
344
+ .router-panel {
345
+ background: #fdfcf9;
346
+ border-radius: 16px;
347
+ padding: 20px 22px;
348
+ border: 1px solid #e4e6ee;
349
+ }
350
+
351
+ .route-title {
352
+ margin: 0 0 8px;
353
+ font-size: 22px;
354
+ }
355
+
356
+ .route-body {
357
+ margin: 0 0 14px;
358
+ font-size: 14px;
359
+ color: var(--rt-muted);
360
+ }
361
+
362
+ .route-hint {
363
+ font-size: 12px;
364
+ color: var(--rt-accent);
365
+ }
366
+ `;
367
+
368
+ jsgui.controls.Router_Control = Router_Control;
369
+ jsgui.controls.Demo_UI = Demo_UI;
370
+ jsgui.controls.router_demo_ui = Demo_UI;
371
+
372
+ module.exports = jsgui;
@@ -0,0 +1,21 @@
1
+ const jsgui = require('./client');
2
+ const Server = require('../../../server');
3
+ const { Demo_UI } = jsgui.controls;
4
+
5
+ if (require.main === module) {
6
+ const server_instance = new Server({
7
+ Ctrl: Demo_UI,
8
+ src_path_client_js: require.resolve('./client.js')
9
+ });
10
+
11
+ server_instance.allowed_addresses = ['127.0.0.1'];
12
+
13
+ server_instance.on('ready', () => {
14
+ server_instance.start(52000, (err) => {
15
+ if (err) {
16
+ throw err;
17
+ }
18
+ console.log('server started on port 52000');
19
+ });
20
+ });
21
+ }