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,764 @@
1
+ const jsgui = require('jsgui3-client');
2
+ const Active_HTML_Document = require('../../../controls/Active_HTML_Document');
3
+ const { Data_Object } = jsgui;
4
+
5
+ const DEFAULT_MIN_ISO = '2024-01-01';
6
+ const DEFAULT_MAX_ISO = '2024-12-31';
7
+ const DEFAULT_ISO_DATE = '2024-06-15';
8
+
9
+ const LOCALE_OPTIONS = [
10
+ { value: 'en-US', label: 'English (US)' },
11
+ { value: 'en-GB', label: 'English (UK)' },
12
+ { value: 'de-DE', label: 'Deutsch (DE)' },
13
+ { value: 'fr-FR', label: 'Francais (FR)' },
14
+ { value: 'ja-JP', label: 'Japanese (JP)' }
15
+ ];
16
+
17
+ class Date_Transform_Control extends jsgui.Control {
18
+ constructor(spec = {}) {
19
+ spec.__type_name = spec.__type_name || 'date_transform_control';
20
+ super(spec);
21
+
22
+ this.data.model = new Data_Object({
23
+ iso_date: spec.iso_date || DEFAULT_ISO_DATE,
24
+ locale: spec.locale || 'en-US',
25
+ min_iso: spec.min_iso || DEFAULT_MIN_ISO,
26
+ max_iso: spec.max_iso || DEFAULT_MAX_ISO
27
+ });
28
+
29
+ this.view.data.model = new Data_Object({
30
+ iso_text: '',
31
+ local_format: '',
32
+ local_input: '',
33
+ local_display: '',
34
+ range_message: '',
35
+ relative_text: '',
36
+ local_error: '',
37
+ iso_error: ''
38
+ });
39
+
40
+ this.setup_bindings();
41
+ this.setup_computed();
42
+ this.setup_watchers();
43
+
44
+ if (!spec.el) {
45
+ this.compose();
46
+ }
47
+ }
48
+
49
+ setup_bindings() {
50
+ this.bind({
51
+ iso_date: {
52
+ to: 'iso_text',
53
+ transform: (iso_date) => iso_date || ''
54
+ }
55
+ });
56
+ }
57
+
58
+ setup_computed() {
59
+ this.computed(
60
+ this.data.model,
61
+ ['locale'],
62
+ (locale) => this.transforms.date.resolve_i18n_format(locale),
63
+ { propertyName: 'local_format', target: this.view.data.model }
64
+ );
65
+
66
+ this.computed(
67
+ this.data.model,
68
+ ['iso_date', 'locale'],
69
+ (iso_date, locale) => this.transforms.date.format_iso_to_locale(iso_date, locale),
70
+ { propertyName: 'local_display', target: this.view.data.model }
71
+ );
72
+
73
+ this.computed(
74
+ this.data.model,
75
+ ['iso_date'],
76
+ (iso_date) => this.transforms.date.relative(iso_date),
77
+ { propertyName: 'relative_text', target: this.view.data.model }
78
+ );
79
+
80
+ this.computed(
81
+ this.data.model,
82
+ ['iso_date'],
83
+ (iso_date) => {
84
+ if (!iso_date) return 'ISO date is required.';
85
+ if (!this.is_iso_valid(iso_date)) return 'ISO date must be YYYY-MM-DD.';
86
+ return '';
87
+ },
88
+ { propertyName: 'iso_error', target: this.view.data.model }
89
+ );
90
+
91
+ this.computed(
92
+ this.data.model,
93
+ ['iso_date', 'min_iso', 'max_iso', 'locale'],
94
+ (iso_date, min_iso, max_iso, locale) => {
95
+ if (!iso_date) return 'Enter a date to see the range.';
96
+ if (!this.is_iso_valid(iso_date)) return 'ISO date must be YYYY-MM-DD.';
97
+ if (!this.is_in_range(iso_date, min_iso, max_iso)) {
98
+ const min_display = this.transforms.date.format_iso_to_locale(min_iso, locale);
99
+ const max_display = this.transforms.date.format_iso_to_locale(max_iso, locale);
100
+ return `Date must be between ${min_display} and ${max_display}.`;
101
+ }
102
+ return 'Date is within range.';
103
+ },
104
+ { propertyName: 'range_message', target: this.view.data.model }
105
+ );
106
+ }
107
+
108
+ setup_watchers() {
109
+ this.watch(
110
+ this.view.data.model,
111
+ 'local_display',
112
+ (local_display) => {
113
+ if (!local_display) return;
114
+ if (this.view.data.model.local_input !== local_display) {
115
+ this.view.data.model.local_input = local_display;
116
+ }
117
+ if (this.view.data.model.local_error) {
118
+ this.view.data.model.local_error = '';
119
+ }
120
+ },
121
+ { immediate: true }
122
+ );
123
+ }
124
+
125
+ is_iso_valid(iso_date) {
126
+ return Boolean(this.transforms.date.iso_to_parts(iso_date));
127
+ }
128
+
129
+ is_in_range(iso_date, min_iso, max_iso) {
130
+ if (!this.is_iso_valid(iso_date)) return false;
131
+ if (min_iso && iso_date < min_iso) return false;
132
+ if (max_iso && iso_date > max_iso) return false;
133
+ return true;
134
+ }
135
+
136
+ update_iso_from_input(raw_value) {
137
+ const next_value = raw_value ? String(raw_value).trim() : '';
138
+ this.data.model.iso_date = next_value;
139
+ }
140
+
141
+ update_iso_from_local_input(raw_value) {
142
+ const locale = this.data.model.locale;
143
+ const format_override = this.view.data.model.local_format ||
144
+ this.transforms.date.resolve_i18n_format(locale);
145
+ const trimmed_value = raw_value ? String(raw_value).trim() : '';
146
+ if (!trimmed_value) {
147
+ this.view.data.model.local_error = '';
148
+ return;
149
+ }
150
+
151
+ const parsed_iso = this.transforms.date.parse_i18n_to_iso(
152
+ trimmed_value,
153
+ locale,
154
+ format_override
155
+ );
156
+
157
+ if (parsed_iso) {
158
+ this.view.data.model.local_error = '';
159
+ this.data.model.iso_date = parsed_iso;
160
+ return;
161
+ }
162
+
163
+ this.view.data.model.local_error = `Invalid date for format ${format_override}.`;
164
+ }
165
+
166
+ activate() {
167
+ if (this.__active) return;
168
+ super.activate();
169
+
170
+ if (this._dom_bound) return;
171
+ const root_el = this.dom.el;
172
+ if (!root_el) return;
173
+ this._dom_bound = true;
174
+
175
+ const locale_select_el = root_el.querySelector('[data-test="locale-select"]');
176
+ const iso_input_el = root_el.querySelector('[data-test="iso-input"]');
177
+ const local_input_el = root_el.querySelector('[data-test="locale-input"]');
178
+ const format_text_el = root_el.querySelector('[data-test="format-text"]');
179
+ const local_display_el = root_el.querySelector('[data-test="local-display"]');
180
+ const relative_display_el = root_el.querySelector('[data-test="relative-display"]');
181
+ const range_message_el = root_el.querySelector('[data-test="range-message"]');
182
+ const iso_error_el = root_el.querySelector('[data-test="iso-error"]');
183
+ const locale_error_el = root_el.querySelector('[data-test="locale-error"]');
184
+ const iso_row_el = iso_input_el ? iso_input_el.closest('.date-row') : null;
185
+ const local_row_el = local_input_el ? local_input_el.closest('.date-row') : null;
186
+
187
+ const set_error_state = (row_el, has_error) => {
188
+ if (!row_el) return;
189
+ row_el.classList.toggle('has-error', Boolean(has_error));
190
+ };
191
+
192
+ if (locale_select_el) {
193
+ locale_select_el.addEventListener('change', (event) => {
194
+ const locale_value = event && event.target ? event.target.value : 'en-US';
195
+ this.data.model.locale = locale_value;
196
+ });
197
+ }
198
+
199
+ if (iso_input_el) {
200
+ iso_input_el.addEventListener('input', (event) => {
201
+ const raw_value = event && event.target ? event.target.value : '';
202
+ this.update_iso_from_input(raw_value);
203
+ });
204
+ }
205
+
206
+ if (local_input_el) {
207
+ local_input_el.addEventListener('input', (event) => {
208
+ const raw_value = event && event.target ? event.target.value : '';
209
+ this.view.data.model.local_input = raw_value;
210
+ this.update_iso_from_local_input(raw_value);
211
+ });
212
+ }
213
+
214
+ this.watch(
215
+ this.data.model,
216
+ 'locale',
217
+ (locale_value) => {
218
+ if (locale_select_el) {
219
+ locale_select_el.value = locale_value;
220
+ }
221
+ },
222
+ { immediate: true }
223
+ );
224
+
225
+ this.watch(
226
+ this.view.data.model,
227
+ 'iso_text',
228
+ (iso_text) => {
229
+ if (iso_input_el) {
230
+ iso_input_el.value = iso_text || '';
231
+ }
232
+ },
233
+ { immediate: true }
234
+ );
235
+
236
+ this.watch(
237
+ this.view.data.model,
238
+ 'local_input',
239
+ (local_value) => {
240
+ if (local_input_el) {
241
+ local_input_el.value = local_value || '';
242
+ }
243
+ },
244
+ { immediate: true }
245
+ );
246
+
247
+ this.watch(
248
+ this.view.data.model,
249
+ 'local_format',
250
+ (local_format) => {
251
+ if (format_text_el) {
252
+ format_text_el.textContent = `Format: ${local_format}`;
253
+ }
254
+ if (local_input_el) {
255
+ local_input_el.placeholder = local_format || '';
256
+ }
257
+ },
258
+ { immediate: true }
259
+ );
260
+
261
+ this.watch(
262
+ this.view.data.model,
263
+ 'local_display',
264
+ (local_display_value) => {
265
+ if (local_display_el) {
266
+ local_display_el.textContent = local_display_value || '';
267
+ }
268
+ },
269
+ { immediate: true }
270
+ );
271
+
272
+ this.watch(
273
+ this.view.data.model,
274
+ 'relative_text',
275
+ (relative_text) => {
276
+ if (relative_display_el) {
277
+ relative_display_el.textContent = relative_text || '';
278
+ }
279
+ },
280
+ { immediate: true }
281
+ );
282
+
283
+ this.watch(
284
+ this.view.data.model,
285
+ 'range_message',
286
+ (range_message) => {
287
+ if (range_message_el) {
288
+ range_message_el.textContent = range_message || '';
289
+ }
290
+ },
291
+ { immediate: true }
292
+ );
293
+
294
+ this.watch(
295
+ this.view.data.model,
296
+ 'iso_error',
297
+ (iso_error_text) => {
298
+ if (iso_error_el) {
299
+ iso_error_el.textContent = iso_error_text || '';
300
+ }
301
+ set_error_state(iso_row_el, Boolean(iso_error_text));
302
+ },
303
+ { immediate: true }
304
+ );
305
+
306
+ this.watch(
307
+ this.view.data.model,
308
+ 'local_error',
309
+ (local_error_text) => {
310
+ if (locale_error_el) {
311
+ locale_error_el.textContent = local_error_text || '';
312
+ }
313
+ set_error_state(local_row_el, Boolean(local_error_text));
314
+ },
315
+ { immediate: true }
316
+ );
317
+ }
318
+
319
+ compose() {
320
+ // Framework expects the method name `compose`.
321
+ const page_context = this.context;
322
+
323
+ this.add_class('date-transform-control');
324
+ this.dom.attributes['data-test'] = 'date-transform-control';
325
+
326
+ const card = new jsgui.Control({
327
+ context: page_context,
328
+ tagName: 'div',
329
+ class: 'date-card'
330
+ });
331
+
332
+ const title = new jsgui.Control({
333
+ context: page_context,
334
+ tagName: 'h1',
335
+ class: 'date-title',
336
+ content: 'Date Transform Lab'
337
+ });
338
+
339
+ const subtitle = new jsgui.Control({
340
+ context: page_context,
341
+ tagName: 'p',
342
+ class: 'date-subtitle',
343
+ content: 'Transform locale-specific dates into ISO and validate ranges.'
344
+ });
345
+
346
+ const form = new jsgui.Control({
347
+ context: page_context,
348
+ tagName: 'div',
349
+ class: 'date-form'
350
+ });
351
+
352
+ const create_row = (label_text, input_control, error_control) => {
353
+ const row = new jsgui.Control({
354
+ context: page_context,
355
+ tagName: 'div',
356
+ class: 'date-row'
357
+ });
358
+
359
+ const label = new jsgui.Control({
360
+ context: page_context,
361
+ tagName: 'label',
362
+ class: 'date-label',
363
+ content: label_text
364
+ });
365
+
366
+ row.add(label);
367
+ row.add(input_control);
368
+ if (error_control) {
369
+ row.add(error_control);
370
+ }
371
+
372
+ return { row, label };
373
+ };
374
+
375
+ const locale_select = new jsgui.Control({
376
+ context: page_context,
377
+ tagName: 'select',
378
+ class: 'date-select'
379
+ });
380
+ locale_select.dom.attributes['data-test'] = 'locale-select';
381
+
382
+ LOCALE_OPTIONS.forEach((option_data) => {
383
+ const option = new jsgui.Control({
384
+ context: page_context,
385
+ tagName: 'option',
386
+ content: option_data.label
387
+ });
388
+ option.dom.attributes.value = option_data.value;
389
+ locale_select.add(option);
390
+ });
391
+
392
+ const locale_row = create_row('Locale', locale_select, null);
393
+
394
+ const iso_input = new jsgui.Control({
395
+ context: page_context,
396
+ tagName: 'input',
397
+ class: 'date-input'
398
+ });
399
+ iso_input.dom.attributes.type = 'text';
400
+ iso_input.dom.attributes.placeholder = 'YYYY-MM-DD';
401
+ iso_input.dom.attributes['data-test'] = 'iso-input';
402
+
403
+ const iso_error = new jsgui.Control({
404
+ context: page_context,
405
+ tagName: 'div',
406
+ class: 'date-error'
407
+ });
408
+ iso_error.dom.attributes['data-test'] = 'iso-error';
409
+
410
+ const iso_row = create_row('ISO date', iso_input, iso_error);
411
+
412
+ const local_input = new jsgui.Control({
413
+ context: page_context,
414
+ tagName: 'input',
415
+ class: 'date-input'
416
+ });
417
+ local_input.dom.attributes.type = 'text';
418
+ local_input.dom.attributes['data-test'] = 'locale-input';
419
+
420
+ const local_error = new jsgui.Control({
421
+ context: page_context,
422
+ tagName: 'div',
423
+ class: 'date-error'
424
+ });
425
+ local_error.dom.attributes['data-test'] = 'locale-error';
426
+
427
+ const local_row = create_row('Locale date', local_input, local_error);
428
+
429
+ const format_text = new jsgui.Control({
430
+ context: page_context,
431
+ tagName: 'div',
432
+ class: 'date-format'
433
+ });
434
+ format_text.dom.attributes['data-test'] = 'format-text';
435
+
436
+ form.add(locale_row.row);
437
+ form.add(iso_row.row);
438
+ form.add(local_row.row);
439
+ form.add(format_text);
440
+
441
+ const outputs = new jsgui.Control({
442
+ context: page_context,
443
+ tagName: 'div',
444
+ class: 'date-outputs'
445
+ });
446
+
447
+ const create_output = (label_text, test_id) => {
448
+ const wrapper = new jsgui.Control({
449
+ context: page_context,
450
+ tagName: 'div',
451
+ class: 'date-output'
452
+ });
453
+
454
+ const label = new jsgui.Control({
455
+ context: page_context,
456
+ tagName: 'div',
457
+ class: 'date-output-label',
458
+ content: label_text
459
+ });
460
+
461
+ const value = new jsgui.Control({
462
+ context: page_context,
463
+ tagName: 'div',
464
+ class: 'date-output-value'
465
+ });
466
+ if (test_id) {
467
+ value.dom.attributes['data-test'] = test_id;
468
+ }
469
+
470
+ wrapper.add(label);
471
+ wrapper.add(value);
472
+ return { wrapper, value };
473
+ };
474
+
475
+ const local_display = create_output('Locale display', 'local-display');
476
+ const relative_display = create_output('Relative', 'relative-display');
477
+ const range_display = create_output('Range check', 'range-message');
478
+
479
+ outputs.add(local_display.wrapper);
480
+ outputs.add(relative_display.wrapper);
481
+ outputs.add(range_display.wrapper);
482
+
483
+ card.add(title);
484
+ card.add(subtitle);
485
+ card.add(form);
486
+ card.add(outputs);
487
+
488
+ this.add(card);
489
+
490
+ locale_select.on('change', (event) => {
491
+ const locale_value = event && event.target ? event.target.value : 'en-US';
492
+ this.data.model.locale = locale_value;
493
+ });
494
+
495
+ iso_input.on('input', (event) => {
496
+ const raw_value = event && event.target ? event.target.value : '';
497
+ this.update_iso_from_input(raw_value);
498
+ });
499
+
500
+ local_input.on('input', (event) => {
501
+ const raw_value = event && event.target ? event.target.value : '';
502
+ this.view.data.model.local_input = raw_value;
503
+ this.update_iso_from_local_input(raw_value);
504
+ });
505
+
506
+ const set_error_state = (row_control, has_error) => {
507
+ if (has_error) {
508
+ row_control.add_class('has-error');
509
+ } else {
510
+ row_control.remove_class('has-error');
511
+ }
512
+ };
513
+
514
+ this.watch(
515
+ this.data.model,
516
+ 'locale',
517
+ (locale_value) => {
518
+ locale_select.dom.attributes.value = locale_value;
519
+ },
520
+ { immediate: true }
521
+ );
522
+
523
+ this.watch(
524
+ this.view.data.model,
525
+ 'iso_text',
526
+ (iso_text) => {
527
+ iso_input.dom.attributes.value = iso_text || '';
528
+ },
529
+ { immediate: true }
530
+ );
531
+
532
+ this.watch(
533
+ this.view.data.model,
534
+ 'local_input',
535
+ (local_value) => {
536
+ local_input.dom.attributes.value = local_value || '';
537
+ },
538
+ { immediate: true }
539
+ );
540
+
541
+ this.watch(
542
+ this.view.data.model,
543
+ 'local_format',
544
+ (local_format) => {
545
+ format_text.clear();
546
+ format_text.add(`Format: ${local_format}`);
547
+ local_input.dom.attributes.placeholder = local_format;
548
+ },
549
+ { immediate: true }
550
+ );
551
+
552
+ this.watch(
553
+ this.view.data.model,
554
+ 'local_display',
555
+ (local_display_value) => {
556
+ local_display.value.clear();
557
+ local_display.value.add(local_display_value || '');
558
+ },
559
+ { immediate: true }
560
+ );
561
+
562
+ this.watch(
563
+ this.view.data.model,
564
+ 'relative_text',
565
+ (relative_text) => {
566
+ relative_display.value.clear();
567
+ relative_display.value.add(relative_text || '');
568
+ },
569
+ { immediate: true }
570
+ );
571
+
572
+ this.watch(
573
+ this.view.data.model,
574
+ 'range_message',
575
+ (range_message) => {
576
+ range_display.value.clear();
577
+ range_display.value.add(range_message || '');
578
+ },
579
+ { immediate: true }
580
+ );
581
+
582
+ this.watch(
583
+ this.view.data.model,
584
+ 'iso_error',
585
+ (iso_error_text) => {
586
+ iso_error.clear();
587
+ if (iso_error_text) {
588
+ iso_error.add(iso_error_text);
589
+ }
590
+ set_error_state(iso_row.row, Boolean(iso_error_text));
591
+ },
592
+ { immediate: true }
593
+ );
594
+
595
+ this.watch(
596
+ this.view.data.model,
597
+ 'local_error',
598
+ (local_error_text) => {
599
+ local_error.clear();
600
+ if (local_error_text) {
601
+ local_error.add(local_error_text);
602
+ }
603
+ set_error_state(local_row.row, Boolean(local_error_text));
604
+ },
605
+ { immediate: true }
606
+ );
607
+ }
608
+ }
609
+
610
+ class Demo_UI extends Active_HTML_Document {
611
+ constructor(spec = {}) {
612
+ spec.__type_name = spec.__type_name || 'date_transform_demo_ui';
613
+ super(spec);
614
+
615
+ if (!spec.el) {
616
+ this.compose();
617
+ }
618
+ }
619
+
620
+ compose() {
621
+ // Framework expects the method name `compose`.
622
+ const page_context = this.context;
623
+ this.body.add_class('date-transform-demo');
624
+
625
+ const date_control = new Date_Transform_Control({
626
+ context: page_context,
627
+ iso_date: DEFAULT_ISO_DATE,
628
+ locale: 'en-US'
629
+ });
630
+
631
+ this.body.add(date_control);
632
+ }
633
+ }
634
+
635
+ Demo_UI.css = `
636
+ * {
637
+ box-sizing: border-box;
638
+ }
639
+
640
+ body {
641
+ margin: 0;
642
+ padding: 0;
643
+ font-family: "Georgia", "Times New Roman", serif;
644
+ background: linear-gradient(135deg, #f9f2e8 0%, #e4eff7 100%);
645
+ color: #1c232e;
646
+ }
647
+
648
+ .date-transform-demo {
649
+ min-height: 100vh;
650
+ display: flex;
651
+ align-items: center;
652
+ justify-content: center;
653
+ padding: 32px;
654
+ }
655
+
656
+ .date-transform-control {
657
+ width: 100%;
658
+ max-width: 560px;
659
+ }
660
+
661
+ .date-card {
662
+ background: #ffffff;
663
+ border-radius: 20px;
664
+ padding: 32px 34px;
665
+ border: 1px solid #e1d7c7;
666
+ box-shadow: 0 24px 50px rgba(27, 35, 52, 0.12);
667
+ }
668
+
669
+ .date-title {
670
+ margin: 0;
671
+ font-size: 24px;
672
+ }
673
+
674
+ .date-subtitle {
675
+ margin: 8px 0 20px;
676
+ color: #4b5464;
677
+ font-size: 14px;
678
+ }
679
+
680
+ .date-form {
681
+ display: grid;
682
+ gap: 12px;
683
+ }
684
+
685
+ .date-row {
686
+ display: grid;
687
+ gap: 8px;
688
+ }
689
+
690
+ .date-row.has-error .date-input {
691
+ border-color: #c03d2f;
692
+ background: #fff2f1;
693
+ }
694
+
695
+ .date-label {
696
+ font-size: 13px;
697
+ letter-spacing: 0.04em;
698
+ text-transform: uppercase;
699
+ color: #6a5d4f;
700
+ }
701
+
702
+ .date-input,
703
+ .date-select {
704
+ padding: 10px 12px;
705
+ border-radius: 10px;
706
+ border: 1px solid #c8ccd3;
707
+ font-size: 15px;
708
+ background: #fbfbfd;
709
+ }
710
+
711
+ .date-select {
712
+ cursor: pointer;
713
+ }
714
+
715
+ .date-error {
716
+ min-height: 16px;
717
+ font-size: 12px;
718
+ color: #c03d2f;
719
+ }
720
+
721
+ .date-format {
722
+ font-size: 13px;
723
+ color: #4b5464;
724
+ margin-top: 2px;
725
+ }
726
+
727
+ .date-outputs {
728
+ margin-top: 22px;
729
+ display: grid;
730
+ gap: 14px;
731
+ }
732
+
733
+ .date-output {
734
+ padding: 12px 14px;
735
+ border-radius: 12px;
736
+ background: #f6f7fb;
737
+ border: 1px solid #e1e5ec;
738
+ }
739
+
740
+ .date-output-label {
741
+ font-size: 12px;
742
+ text-transform: uppercase;
743
+ letter-spacing: 0.05em;
744
+ color: #6f7786;
745
+ margin-bottom: 6px;
746
+ }
747
+
748
+ .date-output-value {
749
+ font-size: 16px;
750
+ color: #1f2a38;
751
+ }
752
+
753
+ @media (max-width: 520px) {
754
+ .date-card {
755
+ padding: 26px;
756
+ }
757
+ }
758
+ `;
759
+
760
+ jsgui.controls.Date_Transform_Control = Date_Transform_Control;
761
+ jsgui.controls.Demo_UI = Demo_UI;
762
+ jsgui.controls.date_transform_demo_ui = Demo_UI;
763
+
764
+ module.exports = jsgui;