jsgui3-server 0.0.143 → 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 (67) hide show
  1. package/docs/comprehensive-documentation.md +25 -6
  2. package/docs/configuration-reference.md +46 -11
  3. package/docs/controls-development.md +54 -26
  4. package/docs/jsgui3-html-improvement-ideas.md +162 -0
  5. package/docs/jsgui3-html-improvement-ideas.svg +151 -0
  6. package/docs/troubleshooting.md +9 -8
  7. package/examples/controls/14d) window, canvas globe/EarthGlobeRenderer.js +19 -14
  8. package/examples/controls/14d) window, canvas globe/pipeline/TransformStage.js +5 -5
  9. package/examples/jsgui3-html/01) mvvm-counter/client.js +648 -0
  10. package/examples/jsgui3-html/01) mvvm-counter/server.js +21 -0
  11. package/examples/jsgui3-html/02) date-transform/client.js +764 -0
  12. package/examples/jsgui3-html/02) date-transform/server.js +21 -0
  13. package/examples/jsgui3-html/03) form-validation/client.js +1045 -0
  14. package/examples/jsgui3-html/03) form-validation/server.js +21 -0
  15. package/examples/jsgui3-html/04) data-grid/client.js +738 -0
  16. package/examples/jsgui3-html/04) data-grid/server.js +21 -0
  17. package/examples/jsgui3-html/05) master-detail/client.js +649 -0
  18. package/examples/jsgui3-html/05) master-detail/server.js +21 -0
  19. package/examples/jsgui3-html/06) theming/client.js +514 -0
  20. package/examples/jsgui3-html/06) theming/server.js +21 -0
  21. package/examples/jsgui3-html/07) mixins/client.js +465 -0
  22. package/examples/jsgui3-html/07) mixins/server.js +21 -0
  23. package/examples/jsgui3-html/08) router/client.js +372 -0
  24. package/examples/jsgui3-html/08) router/server.js +21 -0
  25. package/examples/jsgui3-html/09) resource-transform/client.js +692 -0
  26. package/examples/jsgui3-html/09) resource-transform/server.js +21 -0
  27. package/examples/jsgui3-html/10) binding-debugger/client.js +810 -0
  28. package/examples/jsgui3-html/10) binding-debugger/server.js +21 -0
  29. package/examples/jsgui3-html/README.md +48 -0
  30. package/http/responders/static/Static_Route_HTTP_Responder.js +25 -20
  31. package/lab/README.md +19 -0
  32. package/lab/experiments/window_examples_dom_audit.js +241 -0
  33. package/lab/results/window_examples_dom_audit.json +131 -0
  34. package/lab/results/window_examples_dom_audit.md +46 -0
  35. package/package.json +8 -3
  36. package/publishers/http-webpageorsite-publisher.js +8 -4
  37. package/resources/processors/bundlers/css-bundler.js +28 -173
  38. package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +32 -20
  39. package/resources/processors/bundlers/style-bundler.js +288 -0
  40. package/resources/processors/compilers/SASS_Compiler.js +88 -0
  41. package/resources/processors/extractors/js/css_and_js/AST_Node/CSS_And_JS_From_JS_String_Using_AST_Node_Extractor.js +64 -68
  42. package/resources/website-css-resource.js +24 -20
  43. package/resources/website-javascript-resource-processor.js +17 -57
  44. package/resources/website-javascript-resource.js +17 -57
  45. package/serve-factory.js +38 -24
  46. package/server.js +116 -92
  47. package/tests/README.md +38 -3
  48. package/tests/bundlers.test.js +41 -32
  49. package/tests/content-analysis.test.js +19 -18
  50. package/tests/end-to-end.test.js +336 -365
  51. package/tests/error-handling.test.js +13 -11
  52. package/tests/examples-controls.e2e.test.js +13 -1
  53. package/tests/fixtures/end-to-end-client.js +54 -0
  54. package/tests/fixtures/jsgui3-html/binding_debugger_expectations.json +15 -0
  55. package/tests/fixtures/jsgui3-html/counter_expectations.json +31 -0
  56. package/tests/fixtures/jsgui3-html/data_grid_expectations.json +26 -0
  57. package/tests/fixtures/jsgui3-html/date_transform_expectations.json +26 -0
  58. package/tests/fixtures/jsgui3-html/form_validation_expectations.json +27 -0
  59. package/tests/fixtures/jsgui3-html/master_detail_expectations.json +15 -0
  60. package/tests/fixtures/jsgui3-html/mixins_expectations.json +10 -0
  61. package/tests/fixtures/jsgui3-html/resource_transform_expectations.json +11 -0
  62. package/tests/fixtures/jsgui3-html/router_expectations.json +10 -0
  63. package/tests/fixtures/jsgui3-html/theming_expectations.json +10 -0
  64. package/tests/jsgui3-html-examples.puppeteer.test.js +537 -0
  65. package/tests/sass-controls.e2e.test.js +327 -0
  66. package/tests/test-runner.js +4 -1
  67. package/tests/window-examples.puppeteer.test.js +455 -0
@@ -0,0 +1,514 @@
1
+ const jsgui = require('jsgui3-client');
2
+ const Active_HTML_Document = require('../../../controls/Active_HTML_Document');
3
+ const { Data_Object } = jsgui;
4
+ const { apply_theme_overrides } = require('jsgui3-html/control_mixins/theme');
5
+
6
+ const THEME_OPTIONS = Object.freeze({
7
+ copper: {
8
+ label: 'Copper Studio',
9
+ tokens: {
10
+ bg: '#fdf6ec',
11
+ surface: '#ffffff',
12
+ accent: '#d05f3b',
13
+ ink: '#2a2018',
14
+ muted: '#6f5b4b'
15
+ }
16
+ },
17
+ tide: {
18
+ label: 'Tide Atlas',
19
+ tokens: {
20
+ bg: '#eef4f7',
21
+ surface: '#ffffff',
22
+ accent: '#2b6cb0',
23
+ ink: '#10253b',
24
+ muted: '#5c6b7a'
25
+ }
26
+ }
27
+ });
28
+
29
+ class Theming_Control extends jsgui.Control {
30
+ constructor(spec = {}) {
31
+ spec.__type_name = spec.__type_name || 'theming_control';
32
+ super(spec);
33
+
34
+ this.data.model = new Data_Object({
35
+ theme_name: 'copper'
36
+ });
37
+
38
+ this.view.data.model = new Data_Object({
39
+ theme_label: '',
40
+ accent_value: '',
41
+ surface_value: '',
42
+ bg_value: '',
43
+ ink_value: '',
44
+ muted_value: ''
45
+ });
46
+
47
+ this.apply_theme_from_name(this.data.model.theme_name);
48
+
49
+ if (!spec.el) {
50
+ this.compose();
51
+ }
52
+ }
53
+
54
+ apply_theme_from_name(theme_name, options = {}) {
55
+ const next_theme = THEME_OPTIONS[theme_name] || THEME_OPTIONS.copper;
56
+ const tokens = next_theme.tokens || {};
57
+
58
+ apply_theme_overrides(this, tokens);
59
+
60
+ if (options.dom_el) {
61
+ const dom_el = options.dom_el;
62
+ Object.keys(tokens).forEach((token_name) => {
63
+ dom_el.style.setProperty(`--theme-${token_name}`, tokens[token_name]);
64
+ });
65
+ dom_el.setAttribute('data-theme', theme_name);
66
+ }
67
+
68
+ this.view.data.model.theme_label = next_theme.label;
69
+ this.view.data.model.accent_value = tokens.accent || '';
70
+ this.view.data.model.surface_value = tokens.surface || '';
71
+ this.view.data.model.bg_value = tokens.bg || '';
72
+ this.view.data.model.ink_value = tokens.ink || '';
73
+ this.view.data.model.muted_value = tokens.muted || '';
74
+ }
75
+
76
+ update_theme_buttons(button_map, active_theme) {
77
+ Object.keys(button_map).forEach((theme_key) => {
78
+ const button_el = button_map[theme_key];
79
+ if (!button_el) return;
80
+ const is_active = theme_key === active_theme;
81
+ button_el.classList.toggle('is-active', is_active);
82
+ button_el.setAttribute('aria-pressed', is_active ? 'true' : 'false');
83
+ });
84
+ }
85
+
86
+ activate() {
87
+ if (this.__active) return;
88
+ super.activate();
89
+
90
+ if (this._dom_bound) return;
91
+ const root_el = this.dom.el;
92
+ if (!root_el) return;
93
+ this._dom_bound = true;
94
+
95
+ const theme_label_el = root_el.querySelector('[data-test="theme-name"]');
96
+ const accent_el = root_el.querySelector('[data-test="token-accent"]');
97
+ const surface_el = root_el.querySelector('[data-test="token-surface"]');
98
+ const bg_el = root_el.querySelector('[data-test="token-bg"]');
99
+ const ink_el = root_el.querySelector('[data-test="token-ink"]');
100
+ const muted_el = root_el.querySelector('[data-test="token-muted"]');
101
+
102
+ const button_map = {
103
+ copper: root_el.querySelector('[data-test="theme-copper"]'),
104
+ tide: root_el.querySelector('[data-test="theme-tide"]')
105
+ };
106
+
107
+ Object.keys(button_map).forEach((theme_key) => {
108
+ const button_el = button_map[theme_key];
109
+ if (!button_el) return;
110
+ button_el.addEventListener('click', () => {
111
+ this.data.model.theme_name = theme_key;
112
+ });
113
+ });
114
+
115
+ this.watch(
116
+ this.data.model,
117
+ 'theme_name',
118
+ (theme_name) => {
119
+ this.apply_theme_from_name(theme_name, { dom_el: root_el });
120
+ this.update_theme_buttons(button_map, theme_name);
121
+ },
122
+ { immediate: true }
123
+ );
124
+
125
+ this.watch(
126
+ this.view.data.model,
127
+ 'theme_label',
128
+ (theme_label) => {
129
+ if (theme_label_el) theme_label_el.textContent = theme_label || '';
130
+ },
131
+ { immediate: true }
132
+ );
133
+
134
+ this.watch(
135
+ this.view.data.model,
136
+ 'accent_value',
137
+ (accent_value) => {
138
+ if (accent_el) accent_el.textContent = accent_value || '';
139
+ },
140
+ { immediate: true }
141
+ );
142
+
143
+ this.watch(
144
+ this.view.data.model,
145
+ 'surface_value',
146
+ (surface_value) => {
147
+ if (surface_el) surface_el.textContent = surface_value || '';
148
+ },
149
+ { immediate: true }
150
+ );
151
+
152
+ this.watch(
153
+ this.view.data.model,
154
+ 'bg_value',
155
+ (bg_value) => {
156
+ if (bg_el) bg_el.textContent = bg_value || '';
157
+ },
158
+ { immediate: true }
159
+ );
160
+
161
+ this.watch(
162
+ this.view.data.model,
163
+ 'ink_value',
164
+ (ink_value) => {
165
+ if (ink_el) ink_el.textContent = ink_value || '';
166
+ },
167
+ { immediate: true }
168
+ );
169
+
170
+ this.watch(
171
+ this.view.data.model,
172
+ 'muted_value',
173
+ (muted_value) => {
174
+ if (muted_el) muted_el.textContent = muted_value || '';
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('theming-control');
185
+ this.dom.attributes['data-test'] = 'theming-control';
186
+ this.dom.attributes['data-theme'] = this.data.model.theme_name;
187
+
188
+ const header = new jsgui.Control({
189
+ context: page_context,
190
+ tagName: 'header',
191
+ class: 'theme-header'
192
+ });
193
+
194
+ header.add(new jsgui.Control({
195
+ context: page_context,
196
+ tagName: 'h1',
197
+ class: 'theme-title',
198
+ content: 'Theme Tokens'
199
+ }));
200
+ header.add(new jsgui.Control({
201
+ context: page_context,
202
+ tagName: 'p',
203
+ class: 'theme-subtitle',
204
+ content: 'Switch token sets and watch the UI update.'
205
+ }));
206
+
207
+ const button_row = new jsgui.Control({
208
+ context: page_context,
209
+ tagName: 'div',
210
+ class: 'theme-buttons'
211
+ });
212
+
213
+ const copper_button = new jsgui.Control({
214
+ context: page_context,
215
+ tagName: 'button',
216
+ class: 'theme-button',
217
+ content: 'Copper'
218
+ });
219
+ copper_button.dom.attributes['data-test'] = 'theme-copper';
220
+
221
+ const tide_button = new jsgui.Control({
222
+ context: page_context,
223
+ tagName: 'button',
224
+ class: 'theme-button',
225
+ content: 'Tide'
226
+ });
227
+ tide_button.dom.attributes['data-test'] = 'theme-tide';
228
+
229
+ button_row.add(copper_button);
230
+ button_row.add(tide_button);
231
+
232
+ const layout = new jsgui.Control({
233
+ context: page_context,
234
+ tagName: 'div',
235
+ class: 'theme-layout'
236
+ });
237
+
238
+ const token_panel = new jsgui.Control({
239
+ context: page_context,
240
+ tagName: 'section',
241
+ class: 'theme-panel'
242
+ });
243
+
244
+ const label = new jsgui.Control({
245
+ context: page_context,
246
+ tagName: 'div',
247
+ class: 'theme-label',
248
+ content: this.view.data.model.theme_label
249
+ });
250
+ label.dom.attributes['data-test'] = 'theme-name';
251
+
252
+ const token_list = new jsgui.Control({
253
+ context: page_context,
254
+ tagName: 'div',
255
+ class: 'theme-token-list'
256
+ });
257
+
258
+ const make_token_row = (label_text, test_id, value) => {
259
+ const row = new jsgui.Control({
260
+ context: page_context,
261
+ tagName: 'div',
262
+ class: 'theme-token'
263
+ });
264
+ row.add(new jsgui.Control({
265
+ context: page_context,
266
+ tagName: 'span',
267
+ class: 'token-label',
268
+ content: label_text
269
+ }));
270
+ const value_span = new jsgui.Control({
271
+ context: page_context,
272
+ tagName: 'span',
273
+ class: 'token-value',
274
+ content: value
275
+ });
276
+ value_span.dom.attributes['data-test'] = test_id;
277
+ row.add(value_span);
278
+ return row;
279
+ };
280
+
281
+ token_list.add(make_token_row('Accent', 'token-accent', this.view.data.model.accent_value));
282
+ token_list.add(make_token_row('Surface', 'token-surface', this.view.data.model.surface_value));
283
+ token_list.add(make_token_row('Background', 'token-bg', this.view.data.model.bg_value));
284
+ token_list.add(make_token_row('Ink', 'token-ink', this.view.data.model.ink_value));
285
+ token_list.add(make_token_row('Muted', 'token-muted', this.view.data.model.muted_value));
286
+
287
+ token_panel.add(label);
288
+ token_panel.add(token_list);
289
+
290
+ const preview_panel = new jsgui.Control({
291
+ context: page_context,
292
+ tagName: 'section',
293
+ class: 'theme-panel theme-preview'
294
+ });
295
+
296
+ const preview_card = new jsgui.Control({
297
+ context: page_context,
298
+ tagName: 'div',
299
+ class: 'preview-card'
300
+ });
301
+ preview_card.dom.attributes['data-test'] = 'preview-card';
302
+
303
+ preview_card.add(new jsgui.Control({
304
+ context: page_context,
305
+ tagName: 'h2',
306
+ class: 'preview-title',
307
+ content: 'Preview Card'
308
+ }));
309
+ preview_card.add(new jsgui.Control({
310
+ context: page_context,
311
+ tagName: 'p',
312
+ class: 'preview-body',
313
+ content: 'Tokens drive the colors, borders, and emphasis styles.'
314
+ }));
315
+ preview_card.add(new jsgui.Control({
316
+ context: page_context,
317
+ tagName: 'button',
318
+ class: 'preview-action',
319
+ content: 'Primary action'
320
+ }));
321
+
322
+ preview_panel.add(preview_card);
323
+
324
+ layout.add(token_panel);
325
+ layout.add(preview_panel);
326
+
327
+ this.add(header);
328
+ this.add(button_row);
329
+ this.add(layout);
330
+ }
331
+ }
332
+
333
+ class Demo_UI extends Active_HTML_Document {
334
+ constructor(spec = {}) {
335
+ spec.__type_name = spec.__type_name || 'theming_demo_ui';
336
+ super(spec);
337
+
338
+ if (!spec.el) {
339
+ this.compose();
340
+ }
341
+ }
342
+
343
+ compose() {
344
+ // Framework expects the method name `compose`.
345
+ const page_context = this.context;
346
+ this.body.add_class('theming-demo');
347
+
348
+ const theme_control = new Theming_Control({
349
+ context: page_context
350
+ });
351
+
352
+ this.body.add(theme_control);
353
+ }
354
+ }
355
+
356
+ Demo_UI.css = `
357
+ :root {
358
+ --theme-bg: #fdf6ec;
359
+ --theme-surface: #ffffff;
360
+ --theme-accent: #d05f3b;
361
+ --theme-ink: #2a2018;
362
+ --theme-muted: #6f5b4b;
363
+ }
364
+
365
+ * {
366
+ box-sizing: border-box;
367
+ }
368
+
369
+ body {
370
+ margin: 0;
371
+ padding: 0;
372
+ background: var(--theme-bg);
373
+ color: var(--theme-ink);
374
+ font-family: "Sora", "Trebuchet MS", sans-serif;
375
+ }
376
+
377
+ .theming-demo {
378
+ min-height: 100vh;
379
+ display: flex;
380
+ align-items: center;
381
+ justify-content: center;
382
+ padding: 42px 24px;
383
+ }
384
+
385
+ .theming-control {
386
+ width: min(980px, 100%);
387
+ background: var(--theme-surface);
388
+ border-radius: 24px;
389
+ padding: 32px 36px 40px;
390
+ border: 1px solid rgba(0, 0, 0, 0.08);
391
+ box-shadow: 0 26px 60px rgba(30, 30, 40, 0.15);
392
+ }
393
+
394
+ .theme-header {
395
+ margin-bottom: 18px;
396
+ }
397
+
398
+ .theme-title {
399
+ margin: 0 0 8px;
400
+ font-size: 30px;
401
+ }
402
+
403
+ .theme-subtitle {
404
+ margin: 0;
405
+ color: var(--theme-muted);
406
+ font-size: 15px;
407
+ }
408
+
409
+ .theme-buttons {
410
+ display: flex;
411
+ gap: 10px;
412
+ margin-bottom: 18px;
413
+ }
414
+
415
+ .theme-button {
416
+ border: 1px solid rgba(0, 0, 0, 0.15);
417
+ background: transparent;
418
+ padding: 8px 16px;
419
+ border-radius: 999px;
420
+ font-size: 13px;
421
+ cursor: pointer;
422
+ }
423
+
424
+ .theme-button.is-active {
425
+ background: var(--theme-accent);
426
+ border-color: var(--theme-accent);
427
+ color: #ffffff;
428
+ }
429
+
430
+ .theme-layout {
431
+ display: grid;
432
+ grid-template-columns: 1fr 1fr;
433
+ gap: 20px;
434
+ }
435
+
436
+ .theme-panel {
437
+ background: #fbfaf7;
438
+ border-radius: 16px;
439
+ border: 1px solid rgba(0, 0, 0, 0.08);
440
+ padding: 18px 20px;
441
+ }
442
+
443
+ .theme-label {
444
+ font-size: 18px;
445
+ font-weight: 600;
446
+ margin-bottom: 12px;
447
+ }
448
+
449
+ .theme-token-list {
450
+ display: grid;
451
+ gap: 10px;
452
+ }
453
+
454
+ .theme-token {
455
+ display: flex;
456
+ justify-content: space-between;
457
+ font-size: 13px;
458
+ color: var(--theme-muted);
459
+ }
460
+
461
+ .token-label {
462
+ text-transform: uppercase;
463
+ letter-spacing: 0.08em;
464
+ }
465
+
466
+ .token-value {
467
+ font-weight: 600;
468
+ color: var(--theme-ink);
469
+ }
470
+
471
+ .theme-preview {
472
+ background: linear-gradient(140deg, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.6));
473
+ }
474
+
475
+ .preview-card {
476
+ background: var(--theme-bg);
477
+ border-radius: 16px;
478
+ padding: 20px;
479
+ border: 1px solid rgba(0, 0, 0, 0.06);
480
+ }
481
+
482
+ .preview-title {
483
+ margin: 0 0 8px;
484
+ font-size: 20px;
485
+ }
486
+
487
+ .preview-body {
488
+ margin: 0 0 16px;
489
+ color: var(--theme-muted);
490
+ font-size: 14px;
491
+ }
492
+
493
+ .preview-action {
494
+ background: var(--theme-accent);
495
+ border: none;
496
+ color: #ffffff;
497
+ padding: 10px 16px;
498
+ border-radius: 999px;
499
+ cursor: pointer;
500
+ font-size: 13px;
501
+ }
502
+
503
+ @media (max-width: 900px) {
504
+ .theme-layout {
505
+ grid-template-columns: 1fr;
506
+ }
507
+ }
508
+ `;
509
+
510
+ jsgui.controls.Theming_Control = Theming_Control;
511
+ jsgui.controls.Demo_UI = Demo_UI;
512
+ jsgui.controls.theming_demo_ui = Demo_UI;
513
+
514
+ 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
+ }