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,465 @@
1
+ const jsgui = require('jsgui3-client');
2
+ const Active_HTML_Document = require('../../../controls/Active_HTML_Document');
3
+ const { Data_Object } = jsgui;
4
+
5
+ const CARD_CONFIGS = Object.freeze([
6
+ {
7
+ key: 'select',
8
+ title: 'Selectable Card',
9
+ description: 'Click to toggle selection state.',
10
+ mixins: ['selectable']
11
+ },
12
+ {
13
+ key: 'drag',
14
+ title: 'Dragable Card',
15
+ description: 'Drag within the canvas to explore movement.',
16
+ mixins: ['dragable', 'selectable']
17
+ },
18
+ {
19
+ key: 'resize',
20
+ title: 'Resizable Card',
21
+ description: 'Resize from the corner handle.',
22
+ mixins: ['resizable', 'selectable']
23
+ }
24
+ ]);
25
+
26
+ class Mixins_Demo_Control extends jsgui.Control {
27
+ constructor(spec = {}) {
28
+ spec.__type_name = spec.__type_name || 'mixins_demo_control';
29
+ super(spec);
30
+
31
+ this.card_controls = [];
32
+
33
+ this.data.model = new Data_Object({
34
+ selected_index: 0
35
+ });
36
+
37
+ this.view.data.model = new Data_Object({
38
+ selected_title: '',
39
+ selected_mixins_text: '',
40
+ selected_hint: ''
41
+ });
42
+
43
+ this.setup_watchers();
44
+ this.update_selected_summary();
45
+
46
+ if (!spec.el) {
47
+ this.compose();
48
+ }
49
+ }
50
+
51
+ setup_watchers() {
52
+ this.watch(this.data.model, 'selected_index', () => this.update_selected_summary());
53
+ }
54
+
55
+ get_card_config(index) {
56
+ return CARD_CONFIGS[index] || CARD_CONFIGS[0];
57
+ }
58
+
59
+ set_selected_index(next_index) {
60
+ const max_index = Math.max(0, CARD_CONFIGS.length - 1);
61
+ const clamped_index = Math.max(0, Math.min(max_index, next_index));
62
+ this.data.model.selected_index = clamped_index;
63
+ this.apply_selection_to_cards();
64
+ }
65
+
66
+ update_selected_summary() {
67
+ const selected_index = Number(this.data.model.selected_index) || 0;
68
+ const selected_config = this.get_card_config(selected_index);
69
+ const mixin_text = selected_config.mixins.join(', ');
70
+
71
+ this.view.data.model.selected_title = selected_config.title;
72
+ this.view.data.model.selected_mixins_text = `Mixins: ${mixin_text}`;
73
+ this.view.data.model.selected_hint = selected_config.description;
74
+ }
75
+
76
+ apply_mixins_to_card(card_control, config) {
77
+ if (card_control._mixins_applied) return;
78
+ card_control._mixins_applied = true;
79
+
80
+ jsgui.mx.selectable(card_control);
81
+ card_control.selectable = true;
82
+
83
+ if (config.mixins.includes('dragable')) {
84
+ jsgui.mx.dragable(card_control, { mode: 'translate' });
85
+ }
86
+
87
+ if (config.mixins.includes('resizable')) {
88
+ jsgui.mx.resizable(card_control);
89
+ }
90
+ }
91
+
92
+ apply_selection_to_cards() {
93
+ const selected_index = Number(this.data.model.selected_index) || 0;
94
+ const root_el = this.dom && this.dom.el ? this.dom.el : null;
95
+ this.card_controls.forEach((card_control, index) => {
96
+ const is_selected = index === selected_index;
97
+ if (card_control.selected !== is_selected) {
98
+ card_control.selected = is_selected;
99
+ }
100
+ if (is_selected) {
101
+ card_control.add_class('selected');
102
+ } else {
103
+ card_control.remove_class('selected');
104
+ }
105
+ });
106
+ if (root_el) {
107
+ const card_els = Array.from(root_el.querySelectorAll('[data-index]'));
108
+ card_els.forEach((card_el) => {
109
+ const index_value = Number(card_el.getAttribute('data-index')) || 0;
110
+ card_el.classList.toggle('selected', index_value === selected_index);
111
+ });
112
+ }
113
+ }
114
+
115
+ activate() {
116
+ if (this.__active) return;
117
+ super.activate();
118
+
119
+ if (this._dom_bound) return;
120
+ const root_el = this.dom.el;
121
+ if (!root_el) return;
122
+ this._dom_bound = true;
123
+
124
+ const selected_title_el = root_el.querySelector('[data-test="selected-title"]');
125
+ const selected_mixins_el = root_el.querySelector('[data-test="selected-mixins"]');
126
+ const selected_hint_el = root_el.querySelector('[data-test="selected-hint"]');
127
+
128
+ root_el.addEventListener('click', (event) => {
129
+ let target = event.target;
130
+ while (target && target !== root_el) {
131
+ const index_value = target.getAttribute('data-index');
132
+ if (index_value !== null) {
133
+ this.set_selected_index(Number(index_value));
134
+ break;
135
+ }
136
+ target = target.parentElement;
137
+ }
138
+ });
139
+
140
+ this.card_controls.forEach((card_control, index) => {
141
+ const config = this.get_card_config(index);
142
+ this.apply_mixins_to_card(card_control, config);
143
+ card_control.on('change', (event) => {
144
+ if (event.name === 'selected' && event.value) {
145
+ this.set_selected_index(index);
146
+ }
147
+ });
148
+ });
149
+
150
+ this.apply_selection_to_cards();
151
+
152
+ this.watch(
153
+ this.view.data.model,
154
+ 'selected_title',
155
+ (selected_title) => {
156
+ if (selected_title_el) selected_title_el.textContent = selected_title || '';
157
+ },
158
+ { immediate: true }
159
+ );
160
+
161
+ this.watch(
162
+ this.view.data.model,
163
+ 'selected_mixins_text',
164
+ (selected_mixins_text) => {
165
+ if (selected_mixins_el) selected_mixins_el.textContent = selected_mixins_text || '';
166
+ },
167
+ { immediate: true }
168
+ );
169
+
170
+ this.watch(
171
+ this.view.data.model,
172
+ 'selected_hint',
173
+ (selected_hint) => {
174
+ if (selected_hint_el) selected_hint_el.textContent = selected_hint || '';
175
+ },
176
+ { immediate: true }
177
+ );
178
+ }
179
+
180
+ compose() {
181
+ // Framework expects the method name `compose`.
182
+ const page_context = this.context;
183
+
184
+ this.add_class('mixins-demo-control');
185
+ this.dom.attributes['data-test'] = 'mixins-demo-control';
186
+
187
+ const header = new jsgui.Control({
188
+ context: page_context,
189
+ tagName: 'header',
190
+ class: 'mixins-header'
191
+ });
192
+
193
+ header.add(new jsgui.Control({
194
+ context: page_context,
195
+ tagName: 'h1',
196
+ class: 'mixins-title',
197
+ content: 'Mixins Playground'
198
+ }));
199
+ header.add(new jsgui.Control({
200
+ context: page_context,
201
+ tagName: 'p',
202
+ class: 'mixins-subtitle',
203
+ content: 'Selectable, dragable, and resizable behaviors layered onto cards.'
204
+ }));
205
+
206
+ const layout = new jsgui.Control({
207
+ context: page_context,
208
+ tagName: 'div',
209
+ class: 'mixins-layout'
210
+ });
211
+
212
+ const canvas = new jsgui.Control({
213
+ context: page_context,
214
+ tagName: 'section',
215
+ class: 'mixins-canvas'
216
+ });
217
+
218
+ const card_grid = new jsgui.Control({
219
+ context: page_context,
220
+ tagName: 'div',
221
+ class: 'mixins-grid'
222
+ });
223
+
224
+ CARD_CONFIGS.forEach((config, index) => {
225
+ const card = new jsgui.Control({
226
+ context: page_context,
227
+ tagName: 'div',
228
+ class: 'mixins-card'
229
+ });
230
+ card.dom.attributes['data-index'] = String(index);
231
+ card.dom.attributes['data-test'] = `mixins-card-${config.key}`;
232
+
233
+ card.add(new jsgui.Control({
234
+ context: page_context,
235
+ tagName: 'div',
236
+ class: 'card-title',
237
+ content: config.title
238
+ }));
239
+ card.add(new jsgui.Control({
240
+ context: page_context,
241
+ tagName: 'div',
242
+ class: 'card-body',
243
+ content: config.description
244
+ }));
245
+
246
+ this.card_controls.push(card);
247
+ card_grid.add(card);
248
+ });
249
+
250
+ canvas.add(card_grid);
251
+
252
+ const inspector = new jsgui.Control({
253
+ context: page_context,
254
+ tagName: 'aside',
255
+ class: 'mixins-inspector'
256
+ });
257
+
258
+ inspector.add(new jsgui.Control({
259
+ context: page_context,
260
+ tagName: 'div',
261
+ class: 'inspector-label',
262
+ content: 'Active card'
263
+ }));
264
+
265
+ const selected_title = new jsgui.Control({
266
+ context: page_context,
267
+ tagName: 'div',
268
+ class: 'inspector-title',
269
+ content: this.view.data.model.selected_title
270
+ });
271
+ selected_title.dom.attributes['data-test'] = 'selected-title';
272
+
273
+ const selected_mixins = new jsgui.Control({
274
+ context: page_context,
275
+ tagName: 'div',
276
+ class: 'inspector-mixins',
277
+ content: this.view.data.model.selected_mixins_text
278
+ });
279
+ selected_mixins.dom.attributes['data-test'] = 'selected-mixins';
280
+
281
+ const selected_hint = new jsgui.Control({
282
+ context: page_context,
283
+ tagName: 'div',
284
+ class: 'inspector-hint',
285
+ content: this.view.data.model.selected_hint
286
+ });
287
+ selected_hint.dom.attributes['data-test'] = 'selected-hint';
288
+
289
+ inspector.add(selected_title);
290
+ inspector.add(selected_mixins);
291
+ inspector.add(selected_hint);
292
+
293
+ layout.add(canvas);
294
+ layout.add(inspector);
295
+
296
+ this.add(header);
297
+ this.add(layout);
298
+ }
299
+ }
300
+
301
+ class Demo_UI extends Active_HTML_Document {
302
+ constructor(spec = {}) {
303
+ spec.__type_name = spec.__type_name || 'mixins_demo_ui';
304
+ super(spec);
305
+
306
+ if (!spec.el) {
307
+ this.compose();
308
+ }
309
+ }
310
+
311
+ compose() {
312
+ // Framework expects the method name `compose`.
313
+ const page_context = this.context;
314
+ this.body.add_class('mixins-demo');
315
+
316
+ const mixins_control = new Mixins_Demo_Control({
317
+ context: page_context
318
+ });
319
+
320
+ this.body.add(mixins_control);
321
+ }
322
+ }
323
+
324
+ Demo_UI.css = `
325
+ :root {
326
+ --mx-ink: #141316;
327
+ --mx-muted: #616168;
328
+ --mx-accent: #4b7b63;
329
+ --mx-panel: #ffffff;
330
+ --mx-border: #dfe2d8;
331
+ --mx-bg: #f3f5ee;
332
+ }
333
+
334
+ * {
335
+ box-sizing: border-box;
336
+ }
337
+
338
+ body {
339
+ margin: 0;
340
+ padding: 0;
341
+ background: linear-gradient(160deg, #eef3ec 0%, #f4efe7 55%, #eee7dc 100%);
342
+ color: var(--mx-ink);
343
+ font-family: "Cabinet Grotesk", "Segoe UI", sans-serif;
344
+ }
345
+
346
+ .mixins-demo {
347
+ min-height: 100vh;
348
+ display: flex;
349
+ align-items: center;
350
+ justify-content: center;
351
+ padding: 40px 24px;
352
+ }
353
+
354
+ .mixins-demo-control {
355
+ width: min(1000px, 100%);
356
+ background: var(--mx-panel);
357
+ border-radius: 26px;
358
+ padding: 32px 36px 40px;
359
+ border: 1px solid var(--mx-border);
360
+ box-shadow: 0 28px 70px rgba(26, 24, 18, 0.16);
361
+ }
362
+
363
+ .mixins-header {
364
+ margin-bottom: 20px;
365
+ }
366
+
367
+ .mixins-title {
368
+ margin: 0 0 8px;
369
+ font-size: 30px;
370
+ }
371
+
372
+ .mixins-subtitle {
373
+ margin: 0;
374
+ color: var(--mx-muted);
375
+ font-size: 15px;
376
+ }
377
+
378
+ .mixins-layout {
379
+ display: grid;
380
+ grid-template-columns: 1.2fr 0.8fr;
381
+ gap: 20px;
382
+ }
383
+
384
+ .mixins-canvas {
385
+ background: #fdfcf8;
386
+ border-radius: 18px;
387
+ border: 1px solid #e3e6dc;
388
+ padding: 18px 20px;
389
+ min-height: 260px;
390
+ }
391
+
392
+ .mixins-grid {
393
+ display: grid;
394
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
395
+ gap: 12px;
396
+ }
397
+
398
+ .mixins-card {
399
+ background: #ffffff;
400
+ border-radius: 14px;
401
+ border: 1px solid #e0e2d8;
402
+ padding: 14px 16px;
403
+ min-height: 120px;
404
+ position: relative;
405
+ cursor: pointer;
406
+ }
407
+
408
+ .mixins-card.selected {
409
+ border-color: var(--mx-accent);
410
+ box-shadow: 0 12px 22px rgba(75, 123, 99, 0.2);
411
+ }
412
+
413
+ .card-title {
414
+ font-weight: 600;
415
+ margin-bottom: 6px;
416
+ }
417
+
418
+ .card-body {
419
+ font-size: 13px;
420
+ color: var(--mx-muted);
421
+ }
422
+
423
+ .mixins-inspector {
424
+ background: #fdfcf8;
425
+ border-radius: 18px;
426
+ border: 1px solid #e3e6dc;
427
+ padding: 18px 20px;
428
+ display: grid;
429
+ gap: 12px;
430
+ }
431
+
432
+ .inspector-label {
433
+ text-transform: uppercase;
434
+ letter-spacing: 0.08em;
435
+ font-size: 12px;
436
+ color: var(--mx-muted);
437
+ }
438
+
439
+ .inspector-title {
440
+ font-size: 18px;
441
+ font-weight: 600;
442
+ }
443
+
444
+ .inspector-mixins {
445
+ font-size: 13px;
446
+ color: var(--mx-accent);
447
+ }
448
+
449
+ .inspector-hint {
450
+ font-size: 13px;
451
+ color: var(--mx-muted);
452
+ }
453
+
454
+ @media (max-width: 900px) {
455
+ .mixins-layout {
456
+ grid-template-columns: 1fr;
457
+ }
458
+ }
459
+ `;
460
+
461
+ jsgui.controls.Mixins_Demo_Control = Mixins_Demo_Control;
462
+ jsgui.controls.Demo_UI = Demo_UI;
463
+ jsgui.controls.mixins_demo_ui = Demo_UI;
464
+
465
+ 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
+ }