jsgui3-server 0.0.144 → 0.0.146

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 (71) hide show
  1. package/docs/jsgui3-html-improvement-ideas.md +162 -0
  2. package/docs/jsgui3-html-improvement-ideas.svg +151 -0
  3. package/docs/jsgui3-sass-patterns-book/01-vision-and-goals.md +31 -0
  4. package/docs/jsgui3-sass-patterns-book/02-stack-map.md +40 -0
  5. package/docs/jsgui3-sass-patterns-book/03-control-local-sass.md +60 -0
  6. package/docs/jsgui3-sass-patterns-book/04-extension-and-variants.md +76 -0
  7. package/docs/jsgui3-sass-patterns-book/05-theming-and-tokens.md +54 -0
  8. package/docs/jsgui3-sass-patterns-book/06-workspace-overrides.md +45 -0
  9. package/docs/jsgui3-sass-patterns-book/07-resource-and-bundling.md +46 -0
  10. package/docs/jsgui3-sass-patterns-book/08-examples.md +62 -0
  11. package/docs/jsgui3-sass-patterns-book/09-testing-and-adoption.md +27 -0
  12. package/docs/jsgui3-sass-patterns-book/README.md +23 -0
  13. package/docs/troubleshooting.md +44 -4
  14. package/examples/controls/14) window, canvas/client.js +1 -1
  15. package/examples/controls/14b) window, canvas (improved renderer)/client.js +1 -1
  16. package/examples/controls/14d) window, canvas globe/EarthGlobeRenderer.js +19 -14
  17. package/examples/controls/14d) window, canvas globe/client.js +1 -1
  18. package/examples/controls/14d) window, canvas globe/pipeline/TransformStage.js +5 -5
  19. package/examples/controls/14e) window, canvas multithreaded/client.js +1 -1
  20. package/examples/controls/14f) window, canvas polyglobe/client.js +1 -1
  21. package/examples/controls/16) window, form container/client.js +254 -0
  22. package/examples/controls/16) window, form container/server.js +20 -0
  23. package/examples/controls/17) window, mvvm binding/client.js +530 -0
  24. package/examples/controls/17) window, mvvm binding/server.js +20 -0
  25. package/examples/jsgui3-html/01) mvvm-counter/client.js +648 -0
  26. package/examples/jsgui3-html/01) mvvm-counter/server.js +21 -0
  27. package/examples/jsgui3-html/02) date-transform/client.js +764 -0
  28. package/examples/jsgui3-html/02) date-transform/server.js +21 -0
  29. package/examples/jsgui3-html/03) form-validation/client.js +1045 -0
  30. package/examples/jsgui3-html/03) form-validation/server.js +21 -0
  31. package/examples/jsgui3-html/04) data-grid/client.js +738 -0
  32. package/examples/jsgui3-html/04) data-grid/server.js +21 -0
  33. package/examples/jsgui3-html/05) master-detail/client.js +649 -0
  34. package/examples/jsgui3-html/05) master-detail/server.js +21 -0
  35. package/examples/jsgui3-html/06) theming/client.js +514 -0
  36. package/examples/jsgui3-html/06) theming/server.js +21 -0
  37. package/examples/jsgui3-html/07) mixins/client.js +465 -0
  38. package/examples/jsgui3-html/07) mixins/server.js +21 -0
  39. package/examples/jsgui3-html/08) router/client.js +372 -0
  40. package/examples/jsgui3-html/08) router/server.js +21 -0
  41. package/examples/jsgui3-html/09) resource-transform/client.js +692 -0
  42. package/examples/jsgui3-html/09) resource-transform/server.js +21 -0
  43. package/examples/jsgui3-html/10) binding-debugger/client.js +810 -0
  44. package/examples/jsgui3-html/10) binding-debugger/server.js +21 -0
  45. package/examples/jsgui3-html/README.md +48 -0
  46. package/http/responders/static/Static_Route_HTTP_Responder.js +25 -20
  47. package/package.json +3 -3
  48. package/publishers/http-webpageorsite-publisher.js +3 -1
  49. package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +87 -100
  50. package/resources/processors/bundlers/js/esbuild/Core_JS_Single_File_Minifying_Bundler_Using_ESBuild.js +89 -60
  51. package/serve-factory.js +12 -5
  52. package/server.js +103 -85
  53. package/tests/README.md +52 -9
  54. package/tests/bundlers.test.js +53 -47
  55. package/tests/end-to-end.test.js +336 -365
  56. package/tests/examples-controls.e2e.test.js +15 -1
  57. package/tests/fixtures/end-to-end-client.js +54 -0
  58. package/tests/fixtures/jsgui3-html/binding_debugger_expectations.json +15 -0
  59. package/tests/fixtures/jsgui3-html/counter_expectations.json +31 -0
  60. package/tests/fixtures/jsgui3-html/data_grid_expectations.json +26 -0
  61. package/tests/fixtures/jsgui3-html/date_transform_expectations.json +26 -0
  62. package/tests/fixtures/jsgui3-html/form_validation_expectations.json +27 -0
  63. package/tests/fixtures/jsgui3-html/master_detail_expectations.json +15 -0
  64. package/tests/fixtures/jsgui3-html/mixins_expectations.json +10 -0
  65. package/tests/fixtures/jsgui3-html/resource_transform_expectations.json +11 -0
  66. package/tests/fixtures/jsgui3-html/router_expectations.json +10 -0
  67. package/tests/fixtures/jsgui3-html/theming_expectations.json +10 -0
  68. package/tests/jsgui3-html-examples.puppeteer.test.js +537 -0
  69. package/tests/sass-controls.e2e.test.js +123 -9
  70. package/tests/test-runner.js +1 -0
  71. package/tests/window-examples.puppeteer.test.js +217 -1
@@ -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 = new Server({
7
+ Ctrl: Demo_UI,
8
+ src_path_client_js: require.resolve('./client.js')
9
+ });
10
+
11
+ server.allowed_addresses = ['127.0.0.1'];
12
+
13
+ server.on('ready', () => {
14
+ server.start(52000, (err) => {
15
+ if (err) {
16
+ throw err;
17
+ }
18
+ console.log('server started on port 52000');
19
+ });
20
+ });
21
+ }
@@ -0,0 +1,649 @@
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_ITEMS = Object.freeze([
6
+ {
7
+ id: 'md-1',
8
+ name: 'Aurora Vale',
9
+ role: 'Navigator',
10
+ summary: 'Routes the field team through launch windows.',
11
+ focus: 'Focus: Launch plan'
12
+ },
13
+ {
14
+ id: 'md-2',
15
+ name: 'Beacon Lee',
16
+ role: 'Analyst',
17
+ summary: 'Breaks down the signal into clear priorities.',
18
+ focus: 'Focus: Risk review'
19
+ },
20
+ {
21
+ id: 'md-3',
22
+ name: 'Cascade Rowe',
23
+ role: 'Builder',
24
+ summary: 'Turns notes into resilient systems.',
25
+ focus: 'Focus: Build queue'
26
+ },
27
+ {
28
+ id: 'md-4',
29
+ name: 'Delta Hart',
30
+ role: 'Coordinator',
31
+ summary: 'Keeps the release moving with steady cadence.',
32
+ focus: 'Focus: Milestone prep'
33
+ }
34
+ ]);
35
+
36
+ class Master_Detail_Control extends jsgui.Control {
37
+ constructor(spec = {}) {
38
+ spec.__type_name = spec.__type_name || 'master_detail_control';
39
+ super(spec);
40
+
41
+ this.data.model = new Data_Object({
42
+ selected_index: 0
43
+ });
44
+ this.data.model.set('items', spec.items || DEFAULT_ITEMS, true);
45
+
46
+ this.view.data.model = new Data_Object({
47
+ detail_title: '',
48
+ detail_role: '',
49
+ detail_summary: '',
50
+ detail_focus: '',
51
+ nav_text: '',
52
+ has_prev: false,
53
+ has_next: false
54
+ });
55
+
56
+ this.setup_watchers();
57
+ this.recompute_view();
58
+
59
+ if (!spec.el) {
60
+ this.compose();
61
+ }
62
+ }
63
+
64
+ get_items_array() {
65
+ let raw_items = this.data.model && typeof this.data.model.get === 'function'
66
+ ? this.data.model.get('items')
67
+ : this.data.model.items;
68
+
69
+ if (raw_items && raw_items.__data_value) {
70
+ raw_items = raw_items.value;
71
+ }
72
+
73
+ if (raw_items && typeof raw_items.value === 'function') {
74
+ raw_items = raw_items.value();
75
+ }
76
+
77
+ if (Array.isArray(raw_items)) {
78
+ return raw_items.slice();
79
+ }
80
+
81
+ if (raw_items && typeof raw_items.toArray === 'function') {
82
+ return raw_items.toArray().slice();
83
+ }
84
+
85
+ if (raw_items && Array.isArray(raw_items._arr)) {
86
+ return raw_items._arr.slice();
87
+ }
88
+
89
+ return [];
90
+ }
91
+
92
+ setup_watchers() {
93
+ const recompute = () => this.recompute_view();
94
+ this.watch(this.data.model, 'selected_index', () => recompute());
95
+ this.watch(this.data.model, 'items', () => recompute());
96
+ }
97
+
98
+ set_selected_index(next_index) {
99
+ const items = this.get_items_array();
100
+ const max_index = Math.max(0, items.length - 1);
101
+ const clamped_index = Math.max(0, Math.min(max_index, next_index));
102
+ this.data.model.selected_index = clamped_index;
103
+ }
104
+
105
+ recompute_view() {
106
+ const items = this.get_items_array();
107
+ let selected_index = Number(this.data.model.selected_index) || 0;
108
+
109
+ if (items.length === 0) {
110
+ this.view.data.model.detail_title = 'No selection';
111
+ this.view.data.model.detail_role = 'Role: -';
112
+ this.view.data.model.detail_summary = 'No detail available.';
113
+ this.view.data.model.detail_focus = 'Focus: -';
114
+ this.view.data.model.nav_text = 'No items';
115
+ this.view.data.model.has_prev = false;
116
+ this.view.data.model.has_next = false;
117
+ return;
118
+ }
119
+
120
+ if (selected_index < 0) {
121
+ selected_index = 0;
122
+ this.data.model.selected_index = 0;
123
+ }
124
+ if (selected_index > items.length - 1) {
125
+ selected_index = items.length - 1;
126
+ this.data.model.selected_index = selected_index;
127
+ }
128
+
129
+ const selected_item = items[selected_index];
130
+
131
+ this.view.data.model.detail_title = selected_item ? selected_item.name : 'No selection';
132
+ this.view.data.model.detail_role = selected_item ? `Role: ${selected_item.role}` : 'Role: -';
133
+ this.view.data.model.detail_summary = selected_item ? selected_item.summary : 'No detail available.';
134
+ this.view.data.model.detail_focus = selected_item ? selected_item.focus : 'Focus: -';
135
+ this.view.data.model.nav_text = `Item ${selected_index + 1} of ${items.length}`;
136
+ this.view.data.model.has_prev = selected_index > 0;
137
+ this.view.data.model.has_next = selected_index < items.length - 1;
138
+ }
139
+
140
+ update_list_selection(list_container, selected_index) {
141
+ if (!list_container) return;
142
+ const list_items = Array.from(list_container.querySelectorAll('[data-index]'));
143
+ list_items.forEach((item_el) => {
144
+ const item_index = Number(item_el.getAttribute('data-index')) || 0;
145
+ const is_active = item_index === selected_index;
146
+ item_el.classList.toggle('is-active', is_active);
147
+ item_el.setAttribute('aria-selected', is_active ? 'true' : 'false');
148
+ });
149
+ }
150
+
151
+ update_nav_buttons(prev_button_el, next_button_el, has_prev, has_next) {
152
+ if (prev_button_el) {
153
+ prev_button_el.disabled = !has_prev;
154
+ prev_button_el.classList.toggle('is-disabled', !has_prev);
155
+ }
156
+ if (next_button_el) {
157
+ next_button_el.disabled = !has_next;
158
+ next_button_el.classList.toggle('is-disabled', !has_next);
159
+ }
160
+ }
161
+
162
+ activate() {
163
+ if (this.__active) return;
164
+ super.activate();
165
+
166
+ if (this._dom_bound) return;
167
+ const root_el = this.dom.el;
168
+ if (!root_el) return;
169
+ this._dom_bound = true;
170
+
171
+ const list_container = root_el.querySelector('[data-test="master-list"]');
172
+ const detail_title_el = root_el.querySelector('[data-test="detail-title"]');
173
+ const detail_role_el = root_el.querySelector('[data-test="detail-role"]');
174
+ const detail_summary_el = root_el.querySelector('[data-test="detail-summary"]');
175
+ const detail_focus_el = root_el.querySelector('[data-test="detail-focus"]');
176
+ const nav_text_el = root_el.querySelector('[data-test="nav-text"]');
177
+ const prev_button_el = root_el.querySelector('[data-test="prev-button"]');
178
+ const next_button_el = root_el.querySelector('[data-test="next-button"]');
179
+
180
+ if (list_container) {
181
+ list_container.addEventListener('click', (event) => {
182
+ let target = event.target;
183
+ while (target && target !== list_container) {
184
+ const index_value = target.getAttribute('data-index');
185
+ if (index_value !== null) {
186
+ this.set_selected_index(Number(index_value));
187
+ break;
188
+ }
189
+ target = target.parentElement;
190
+ }
191
+ });
192
+ }
193
+
194
+ if (prev_button_el) {
195
+ prev_button_el.addEventListener('click', () => {
196
+ this.set_selected_index(this.data.model.selected_index - 1);
197
+ });
198
+ }
199
+
200
+ if (next_button_el) {
201
+ next_button_el.addEventListener('click', () => {
202
+ this.set_selected_index(this.data.model.selected_index + 1);
203
+ });
204
+ }
205
+
206
+ this.watch(
207
+ this.view.data.model,
208
+ 'detail_title',
209
+ (detail_title) => {
210
+ if (detail_title_el) detail_title_el.textContent = detail_title || '';
211
+ },
212
+ { immediate: true }
213
+ );
214
+
215
+ this.watch(
216
+ this.view.data.model,
217
+ 'detail_role',
218
+ (detail_role) => {
219
+ if (detail_role_el) detail_role_el.textContent = detail_role || '';
220
+ },
221
+ { immediate: true }
222
+ );
223
+
224
+ this.watch(
225
+ this.view.data.model,
226
+ 'detail_summary',
227
+ (detail_summary) => {
228
+ if (detail_summary_el) detail_summary_el.textContent = detail_summary || '';
229
+ },
230
+ { immediate: true }
231
+ );
232
+
233
+ this.watch(
234
+ this.view.data.model,
235
+ 'detail_focus',
236
+ (detail_focus) => {
237
+ if (detail_focus_el) detail_focus_el.textContent = detail_focus || '';
238
+ },
239
+ { immediate: true }
240
+ );
241
+
242
+ this.watch(
243
+ this.view.data.model,
244
+ 'nav_text',
245
+ (nav_text) => {
246
+ if (nav_text_el) nav_text_el.textContent = nav_text || '';
247
+ },
248
+ { immediate: true }
249
+ );
250
+
251
+ this.watch(
252
+ this.view.data.model,
253
+ ['has_prev', 'has_next'],
254
+ () => {
255
+ this.update_nav_buttons(
256
+ prev_button_el,
257
+ next_button_el,
258
+ !!this.view.data.model.has_prev,
259
+ !!this.view.data.model.has_next
260
+ );
261
+ },
262
+ { immediate: true }
263
+ );
264
+
265
+ this.watch(
266
+ this.data.model,
267
+ 'selected_index',
268
+ (selected_index) => {
269
+ this.update_list_selection(list_container, Number(selected_index) || 0);
270
+ },
271
+ { immediate: true }
272
+ );
273
+ }
274
+
275
+ compose() {
276
+ // Framework expects the method name `compose`.
277
+ const page_context = this.context;
278
+ const items = this.get_items_array();
279
+ const selected_index = Number(this.data.model.selected_index) || 0;
280
+
281
+ this.add_class('master-detail-control');
282
+ this.dom.attributes['data-test'] = 'master-detail-control';
283
+
284
+ const header = new jsgui.Control({
285
+ context: page_context,
286
+ tagName: 'header',
287
+ class: 'md-header'
288
+ });
289
+
290
+ header.add(new jsgui.Control({
291
+ context: page_context,
292
+ tagName: 'h1',
293
+ class: 'md-title',
294
+ content: 'Crew Assignments'
295
+ }));
296
+ header.add(new jsgui.Control({
297
+ context: page_context,
298
+ tagName: 'p',
299
+ class: 'md-subtitle',
300
+ content: 'Select a profile to see the detail panel update.'
301
+ }));
302
+
303
+ const layout = new jsgui.Control({
304
+ context: page_context,
305
+ tagName: 'div',
306
+ class: 'md-layout'
307
+ });
308
+
309
+ const list_panel = new jsgui.Control({
310
+ context: page_context,
311
+ tagName: 'section',
312
+ class: 'md-panel md-list-panel'
313
+ });
314
+
315
+ const list_title = new jsgui.Control({
316
+ context: page_context,
317
+ tagName: 'h2',
318
+ class: 'md-panel-title',
319
+ content: 'Roster'
320
+ });
321
+
322
+ const list = new jsgui.Control({
323
+ context: page_context,
324
+ tagName: 'ul',
325
+ class: 'md-list'
326
+ });
327
+ list.dom.attributes['data-test'] = 'master-list';
328
+
329
+ items.forEach((item, index) => {
330
+ const list_item = new jsgui.Control({
331
+ context: page_context,
332
+ tagName: 'li',
333
+ class: 'md-item'
334
+ });
335
+ if (index === selected_index) {
336
+ list_item.add_class('is-active');
337
+ }
338
+ list_item.dom.attributes['data-index'] = String(index);
339
+ list_item.dom.attributes['data-test'] = `master-item-${index}`;
340
+ list_item.dom.attributes.role = 'option';
341
+ list_item.dom.attributes['aria-selected'] = index === selected_index ? 'true' : 'false';
342
+
343
+ list_item.add(new jsgui.Control({
344
+ context: page_context,
345
+ tagName: 'div',
346
+ class: 'md-item-name',
347
+ content: item.name
348
+ }));
349
+ list_item.add(new jsgui.Control({
350
+ context: page_context,
351
+ tagName: 'div',
352
+ class: 'md-item-role',
353
+ content: item.role
354
+ }));
355
+
356
+ list.add(list_item);
357
+ });
358
+
359
+ list_panel.add(list_title);
360
+ list_panel.add(list);
361
+
362
+ const detail_panel = new jsgui.Control({
363
+ context: page_context,
364
+ tagName: 'section',
365
+ class: 'md-panel md-detail-panel'
366
+ });
367
+
368
+ const detail_title = new jsgui.Control({
369
+ context: page_context,
370
+ tagName: 'h2',
371
+ class: 'md-detail-title',
372
+ content: this.view.data.model.detail_title
373
+ });
374
+ detail_title.dom.attributes['data-test'] = 'detail-title';
375
+
376
+ const detail_role = new jsgui.Control({
377
+ context: page_context,
378
+ tagName: 'div',
379
+ class: 'md-detail-role',
380
+ content: this.view.data.model.detail_role
381
+ });
382
+ detail_role.dom.attributes['data-test'] = 'detail-role';
383
+
384
+ const detail_summary = new jsgui.Control({
385
+ context: page_context,
386
+ tagName: 'p',
387
+ class: 'md-detail-summary',
388
+ content: this.view.data.model.detail_summary
389
+ });
390
+ detail_summary.dom.attributes['data-test'] = 'detail-summary';
391
+
392
+ const detail_focus = new jsgui.Control({
393
+ context: page_context,
394
+ tagName: 'div',
395
+ class: 'md-detail-focus',
396
+ content: this.view.data.model.detail_focus
397
+ });
398
+ detail_focus.dom.attributes['data-test'] = 'detail-focus';
399
+
400
+ const nav_bar = new jsgui.Control({
401
+ context: page_context,
402
+ tagName: 'div',
403
+ class: 'md-nav'
404
+ });
405
+
406
+ const nav_text = new jsgui.Control({
407
+ context: page_context,
408
+ tagName: 'div',
409
+ class: 'md-nav-text',
410
+ content: this.view.data.model.nav_text
411
+ });
412
+ nav_text.dom.attributes['data-test'] = 'nav-text';
413
+
414
+ const nav_buttons = new jsgui.Control({
415
+ context: page_context,
416
+ tagName: 'div',
417
+ class: 'md-nav-buttons'
418
+ });
419
+
420
+ const prev_button = new jsgui.Control({
421
+ context: page_context,
422
+ tagName: 'button',
423
+ class: 'md-button',
424
+ content: 'Prev'
425
+ });
426
+ prev_button.dom.attributes['data-test'] = 'prev-button';
427
+
428
+ const next_button = new jsgui.Control({
429
+ context: page_context,
430
+ tagName: 'button',
431
+ class: 'md-button',
432
+ content: 'Next'
433
+ });
434
+ next_button.dom.attributes['data-test'] = 'next-button';
435
+
436
+ nav_buttons.add(prev_button);
437
+ nav_buttons.add(next_button);
438
+
439
+ nav_bar.add(nav_text);
440
+ nav_bar.add(nav_buttons);
441
+
442
+ detail_panel.add(detail_title);
443
+ detail_panel.add(detail_role);
444
+ detail_panel.add(detail_summary);
445
+ detail_panel.add(detail_focus);
446
+ detail_panel.add(nav_bar);
447
+
448
+ layout.add(list_panel);
449
+ layout.add(detail_panel);
450
+
451
+ this.add(header);
452
+ this.add(layout);
453
+ }
454
+ }
455
+
456
+ class Demo_UI extends Active_HTML_Document {
457
+ constructor(spec = {}) {
458
+ spec.__type_name = spec.__type_name || 'master_detail_demo_ui';
459
+ super(spec);
460
+
461
+ if (!spec.el) {
462
+ this.compose();
463
+ }
464
+ }
465
+
466
+ compose() {
467
+ // Framework expects the method name `compose`.
468
+ const page_context = this.context;
469
+ this.body.add_class('master-detail-demo');
470
+
471
+ const master_detail = new Master_Detail_Control({
472
+ context: page_context,
473
+ items: DEFAULT_ITEMS
474
+ });
475
+
476
+ this.body.add(master_detail);
477
+ }
478
+ }
479
+
480
+ Demo_UI.css = `
481
+ :root {
482
+ --md-ink: #1c1b1f;
483
+ --md-muted: #6d6670;
484
+ --md-accent: #cf6d4f;
485
+ --md-panel: #ffffff;
486
+ --md-border: #e2d6cb;
487
+ --md-bg: #f4ede6;
488
+ }
489
+
490
+ * {
491
+ box-sizing: border-box;
492
+ }
493
+
494
+ body {
495
+ margin: 0;
496
+ padding: 0;
497
+ background: radial-gradient(circle at top, #f8f2ec 0%, #efe4d7 55%, #e5d9cf 100%);
498
+ color: var(--md-ink);
499
+ font-family: "Fraunces", "Georgia", serif;
500
+ }
501
+
502
+ .master-detail-demo {
503
+ min-height: 100vh;
504
+ display: flex;
505
+ align-items: center;
506
+ justify-content: center;
507
+ padding: 40px 24px;
508
+ }
509
+
510
+ .master-detail-control {
511
+ width: min(980px, 100%);
512
+ background: var(--md-panel);
513
+ border-radius: 22px;
514
+ padding: 32px 36px 40px;
515
+ border: 1px solid var(--md-border);
516
+ box-shadow: 0 26px 60px rgba(29, 25, 20, 0.18);
517
+ }
518
+
519
+ .md-header {
520
+ margin-bottom: 22px;
521
+ }
522
+
523
+ .md-title {
524
+ margin: 0 0 8px;
525
+ font-size: 30px;
526
+ }
527
+
528
+ .md-subtitle {
529
+ margin: 0;
530
+ color: var(--md-muted);
531
+ font-size: 15px;
532
+ }
533
+
534
+ .md-layout {
535
+ display: grid;
536
+ grid-template-columns: 1fr 1.2fr;
537
+ gap: 20px;
538
+ }
539
+
540
+ .md-panel {
541
+ background: #fdfaf6;
542
+ border: 1px solid #efe2d7;
543
+ border-radius: 16px;
544
+ padding: 18px 20px;
545
+ }
546
+
547
+ .md-panel-title {
548
+ margin: 0 0 12px;
549
+ font-size: 16px;
550
+ }
551
+
552
+ .md-list {
553
+ list-style: none;
554
+ margin: 0;
555
+ padding: 0;
556
+ display: grid;
557
+ gap: 10px;
558
+ }
559
+
560
+ .md-item {
561
+ padding: 12px 14px;
562
+ border-radius: 12px;
563
+ border: 1px solid transparent;
564
+ cursor: pointer;
565
+ background: #fff9f2;
566
+ }
567
+
568
+ .md-item.is-active {
569
+ border-color: var(--md-accent);
570
+ box-shadow: 0 10px 18px rgba(207, 109, 79, 0.2);
571
+ }
572
+
573
+ .md-item-name {
574
+ font-weight: 600;
575
+ font-size: 15px;
576
+ }
577
+
578
+ .md-item-role {
579
+ font-size: 12px;
580
+ color: var(--md-muted);
581
+ }
582
+
583
+ .md-detail-title {
584
+ margin: 0 0 6px;
585
+ font-size: 22px;
586
+ }
587
+
588
+ .md-detail-role {
589
+ font-size: 13px;
590
+ text-transform: uppercase;
591
+ letter-spacing: 0.08em;
592
+ color: var(--md-muted);
593
+ }
594
+
595
+ .md-detail-summary {
596
+ margin: 14px 0;
597
+ font-size: 14px;
598
+ }
599
+
600
+ .md-detail-focus {
601
+ font-size: 13px;
602
+ color: var(--md-accent);
603
+ }
604
+
605
+ .md-nav {
606
+ margin-top: 18px;
607
+ display: flex;
608
+ align-items: center;
609
+ justify-content: space-between;
610
+ gap: 12px;
611
+ }
612
+
613
+ .md-nav-text {
614
+ font-size: 12px;
615
+ color: var(--md-muted);
616
+ }
617
+
618
+ .md-nav-buttons {
619
+ display: flex;
620
+ gap: 10px;
621
+ }
622
+
623
+ .md-button {
624
+ border: 1px solid #d9c8bc;
625
+ background: #f7efe6;
626
+ padding: 8px 14px;
627
+ border-radius: 999px;
628
+ font-size: 12px;
629
+ cursor: pointer;
630
+ font-family: "Assistant", "Arial", sans-serif;
631
+ }
632
+
633
+ .md-button.is-disabled {
634
+ opacity: 0.5;
635
+ cursor: not-allowed;
636
+ }
637
+
638
+ @media (max-width: 880px) {
639
+ .md-layout {
640
+ grid-template-columns: 1fr;
641
+ }
642
+ }
643
+ `;
644
+
645
+ jsgui.controls.Master_Detail_Control = Master_Detail_Control;
646
+ jsgui.controls.Demo_UI = Demo_UI;
647
+ jsgui.controls.master_detail_demo_ui = Demo_UI;
648
+
649
+ 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
+ }