zuzu-js 0.1.0

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 (167) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +113 -0
  3. package/bin/zuzu +17 -0
  4. package/bin/zuzu-build-browser-bundle +57 -0
  5. package/bin/zuzu-generate-browser-stdlib +584 -0
  6. package/bin/zuzu-js +23 -0
  7. package/bin/zuzu-js-compile +152 -0
  8. package/bin/zuzu-js-electron +19 -0
  9. package/dist/zuzu-browser-worker.js +45574 -0
  10. package/dist/zuzu-browser.js +45362 -0
  11. package/lib/browser-bundle-entry.js +160 -0
  12. package/lib/browser-gui-renderer.js +387 -0
  13. package/lib/browser-runtime.js +167 -0
  14. package/lib/browser-worker-entry.js +413 -0
  15. package/lib/browser-ztests/runner.html +103 -0
  16. package/lib/browser-ztests/runner.js +369 -0
  17. package/lib/cli.js +350 -0
  18. package/lib/collections.js +367 -0
  19. package/lib/compiler.js +303 -0
  20. package/lib/electron/launcher.js +70 -0
  21. package/lib/electron/main.js +956 -0
  22. package/lib/electron/preload.js +80 -0
  23. package/lib/electron/renderer.html +122 -0
  24. package/lib/electron/renderer.js +24 -0
  25. package/lib/execution-metadata.js +18 -0
  26. package/lib/gui/dom-renderer.js +778 -0
  27. package/lib/host/browser-host.js +278 -0
  28. package/lib/host/capabilities.js +47 -0
  29. package/lib/host/electron-host.js +15 -0
  30. package/lib/host/node-host.js +74 -0
  31. package/lib/paths.js +150 -0
  32. package/lib/runtime-entrypoints.js +60 -0
  33. package/lib/runtime-helpers.js +886 -0
  34. package/lib/runtime.js +3529 -0
  35. package/lib/tap.js +37 -0
  36. package/lib/transpiler-new/ast.js +23 -0
  37. package/lib/transpiler-new/codegen.js +2455 -0
  38. package/lib/transpiler-new/errors.js +28 -0
  39. package/lib/transpiler-new/index.js +26 -0
  40. package/lib/transpiler-new/lexer.js +834 -0
  41. package/lib/transpiler-new/parser.js +2332 -0
  42. package/lib/transpiler-new/validate-bindings.js +326 -0
  43. package/lib/transpiler-utils.js +95 -0
  44. package/lib/transpiler.js +33 -0
  45. package/lib/zuzu.js +53 -0
  46. package/modules/javascript.js +193 -0
  47. package/modules/std/archive.js +603 -0
  48. package/modules/std/clib.js +338 -0
  49. package/modules/std/data/csv.js +1331 -0
  50. package/modules/std/data/json.js +531 -0
  51. package/modules/std/data/xml.js +441 -0
  52. package/modules/std/data/yaml.js +256 -0
  53. package/modules/std/db-worker.js +250 -0
  54. package/modules/std/db.js +664 -0
  55. package/modules/std/digest/_hash.js +443 -0
  56. package/modules/std/digest/md5.js +26 -0
  57. package/modules/std/digest/sha.js +72 -0
  58. package/modules/std/eval.js +10 -0
  59. package/modules/std/gui/objects.js +1519 -0
  60. package/modules/std/internals.js +571 -0
  61. package/modules/std/io/socks-worker.js +318 -0
  62. package/modules/std/io/socks.js +186 -0
  63. package/modules/std/io.js +475 -0
  64. package/modules/std/marshal/cbor.js +463 -0
  65. package/modules/std/marshal/graph.js +1624 -0
  66. package/modules/std/marshal.js +87 -0
  67. package/modules/std/math/bignum.js +91 -0
  68. package/modules/std/math.js +79 -0
  69. package/modules/std/net/dns.js +306 -0
  70. package/modules/std/net/http.js +820 -0
  71. package/modules/std/net/smtp.js +943 -0
  72. package/modules/std/net/url.js +109 -0
  73. package/modules/std/proc.js +602 -0
  74. package/modules/std/secure.js +3724 -0
  75. package/modules/std/string/base64.js +138 -0
  76. package/modules/std/string.js +299 -0
  77. package/modules/std/task.js +914 -0
  78. package/modules/std/time.js +579 -0
  79. package/modules/std/tui.js +188 -0
  80. package/modules/std/worker-thread.js +246 -0
  81. package/modules/std/worker.js +790 -0
  82. package/package.json +67 -0
  83. package/stdlib/modules/javascript.zzm +99 -0
  84. package/stdlib/modules/perl.zzm +105 -0
  85. package/stdlib/modules/std/archive.zzm +132 -0
  86. package/stdlib/modules/std/cache/lru.zzm +174 -0
  87. package/stdlib/modules/std/clib.zzm +112 -0
  88. package/stdlib/modules/std/colour.zzm +220 -0
  89. package/stdlib/modules/std/config.zzm +818 -0
  90. package/stdlib/modules/std/data/cbor.zzm +497 -0
  91. package/stdlib/modules/std/data/csv.zzm +285 -0
  92. package/stdlib/modules/std/data/ini.zzm +472 -0
  93. package/stdlib/modules/std/data/json/schema/core.zzm +573 -0
  94. package/stdlib/modules/std/data/json/schema/format.zzm +581 -0
  95. package/stdlib/modules/std/data/json/schema/model.zzm +255 -0
  96. package/stdlib/modules/std/data/json/schema/output.zzm +272 -0
  97. package/stdlib/modules/std/data/json/schema/relative_pointer.zzm +299 -0
  98. package/stdlib/modules/std/data/json/schema/validation.zzm +1503 -0
  99. package/stdlib/modules/std/data/json/schema.zzm +306 -0
  100. package/stdlib/modules/std/data/json.zzm +102 -0
  101. package/stdlib/modules/std/data/kdl/json.zzm +460 -0
  102. package/stdlib/modules/std/data/kdl/xml.zzm +387 -0
  103. package/stdlib/modules/std/data/kdl.zzm +1631 -0
  104. package/stdlib/modules/std/data/toml.zzm +756 -0
  105. package/stdlib/modules/std/data/toon.zzm +1017 -0
  106. package/stdlib/modules/std/data/xml/escape.zzm +156 -0
  107. package/stdlib/modules/std/data/xml.zzm +276 -0
  108. package/stdlib/modules/std/data/yaml.zzm +94 -0
  109. package/stdlib/modules/std/db.zzm +173 -0
  110. package/stdlib/modules/std/defer.zzm +75 -0
  111. package/stdlib/modules/std/digest/crc32.zzm +196 -0
  112. package/stdlib/modules/std/digest/md5.zzm +54 -0
  113. package/stdlib/modules/std/digest/sha.zzm +83 -0
  114. package/stdlib/modules/std/dump.zzm +317 -0
  115. package/stdlib/modules/std/eval.zzm +63 -0
  116. package/stdlib/modules/std/getopt.zzm +432 -0
  117. package/stdlib/modules/std/gui/dialogue.zzm +592 -0
  118. package/stdlib/modules/std/gui/objects.zzm +123 -0
  119. package/stdlib/modules/std/gui.zzm +1914 -0
  120. package/stdlib/modules/std/internals.zzm +139 -0
  121. package/stdlib/modules/std/io/socks.zzm +139 -0
  122. package/stdlib/modules/std/io.zzm +157 -0
  123. package/stdlib/modules/std/lingua/en.zzm +347 -0
  124. package/stdlib/modules/std/log.zzm +169 -0
  125. package/stdlib/modules/std/mail.zzm +2726 -0
  126. package/stdlib/modules/std/marshal.zzm +138 -0
  127. package/stdlib/modules/std/math/bignum.zzm +98 -0
  128. package/stdlib/modules/std/math/range.zzm +116 -0
  129. package/stdlib/modules/std/math/roman.zzm +156 -0
  130. package/stdlib/modules/std/math.zzm +141 -0
  131. package/stdlib/modules/std/net/dns.zzm +93 -0
  132. package/stdlib/modules/std/net/http.zzm +278 -0
  133. package/stdlib/modules/std/net/smtp.zzm +257 -0
  134. package/stdlib/modules/std/net/url.zzm +69 -0
  135. package/stdlib/modules/std/path/jsonpointer.zzm +526 -0
  136. package/stdlib/modules/std/path/kdl.zzm +1003 -0
  137. package/stdlib/modules/std/path/simple.zzm +520 -0
  138. package/stdlib/modules/std/path/z/context.zzm +147 -0
  139. package/stdlib/modules/std/path/z/evaluate.zzm +549 -0
  140. package/stdlib/modules/std/path/z/functions.zzm +874 -0
  141. package/stdlib/modules/std/path/z/lexer.zzm +490 -0
  142. package/stdlib/modules/std/path/z/node.zzm +1455 -0
  143. package/stdlib/modules/std/path/z/operators.zzm +445 -0
  144. package/stdlib/modules/std/path/z/parser.zzm +359 -0
  145. package/stdlib/modules/std/path/z.zzm +403 -0
  146. package/stdlib/modules/std/path/zz/functions.zzm +828 -0
  147. package/stdlib/modules/std/path/zz/operators.zzm +1036 -0
  148. package/stdlib/modules/std/path/zz.zzm +100 -0
  149. package/stdlib/modules/std/proc.zzm +155 -0
  150. package/stdlib/modules/std/result.zzm +149 -0
  151. package/stdlib/modules/std/secure.zzm +606 -0
  152. package/stdlib/modules/std/string/base64.zzm +66 -0
  153. package/stdlib/modules/std/string/quoted_printable.zzm +485 -0
  154. package/stdlib/modules/std/string.zzm +179 -0
  155. package/stdlib/modules/std/task.zzm +221 -0
  156. package/stdlib/modules/std/template/z.zzm +531 -0
  157. package/stdlib/modules/std/template/zz.zzm +62 -0
  158. package/stdlib/modules/std/time.zzm +188 -0
  159. package/stdlib/modules/std/tui.zzm +89 -0
  160. package/stdlib/modules/std/uuid.zzm +223 -0
  161. package/stdlib/modules/std/web/session.zzm +388 -0
  162. package/stdlib/modules/std/web/static.zzm +329 -0
  163. package/stdlib/modules/std/web.zzm +1942 -0
  164. package/stdlib/modules/std/worker.zzm +202 -0
  165. package/stdlib/modules/std/zuzuzoo.zzm +3960 -0
  166. package/stdlib/modules/test/more.zzm +528 -0
  167. package/stdlib/modules/test/parser.zzm +209 -0
@@ -0,0 +1,778 @@
1
+ 'use strict';
2
+
3
+ function installSafeDefineProperties() {
4
+ if ( Object.__zuzu_safe_define_properties ) {
5
+ return;
6
+ }
7
+ const nativeDefineProperty = Object.defineProperty;
8
+ const nativeDefineProperties = Object.defineProperties;
9
+ function safeDescriptor( descriptor ) {
10
+ if ( !descriptor || typeof descriptor !== 'object' ) {
11
+ return descriptor;
12
+ }
13
+ const safe = Object.create( null );
14
+ for ( const key of [
15
+ 'value',
16
+ 'get',
17
+ 'set',
18
+ 'writable',
19
+ 'enumerable',
20
+ 'configurable',
21
+ ] ) {
22
+ if ( Object.prototype.hasOwnProperty.call( descriptor, key ) ) {
23
+ safe[key] = descriptor[key];
24
+ }
25
+ }
26
+ return safe;
27
+ }
28
+ Object.defineProperty = function zuzuSafeDefineProperty(
29
+ obj,
30
+ prop,
31
+ descriptor
32
+ ) {
33
+ return nativeDefineProperty( obj, prop, safeDescriptor( descriptor ) );
34
+ };
35
+ Object.defineProperties = function zuzuSafeDefineProperties(
36
+ obj,
37
+ descriptors
38
+ ) {
39
+ const safe = Object.create( null );
40
+ for ( const key of Object.keys( descriptors || {} ) ) {
41
+ safe[key] = safeDescriptor( descriptors[key] );
42
+ }
43
+ return nativeDefineProperties( obj, safe );
44
+ };
45
+ nativeDefineProperty( Object, '__zuzu_safe_define_properties', {
46
+ value: true,
47
+ enumerable: false,
48
+ configurable: false,
49
+ } );
50
+ }
51
+
52
+ let activeState = null;
53
+
54
+ function rendererState() {
55
+ if ( !activeState ) {
56
+ throw new Error( 'Zuzu GUI DOM renderer is not active' );
57
+ }
58
+ return activeState;
59
+ }
60
+
61
+ function rendererDocument() {
62
+ const state = rendererState();
63
+ return state.document || document;
64
+ }
65
+
66
+ function propValue( props, name, fallback = '' ) {
67
+ return props && Object.prototype.hasOwnProperty.call( props, name )
68
+ ? props[name]
69
+ : fallback;
70
+ }
71
+
72
+ function itemLabel( item ) {
73
+ if ( item == null ) {
74
+ return '';
75
+ }
76
+ if ( typeof item === 'string' || typeof item === 'number' ) {
77
+ return String( item );
78
+ }
79
+ return String( item.label ?? item.value ?? '' );
80
+ }
81
+
82
+ function itemValue( item ) {
83
+ if ( item == null ) {
84
+ return '';
85
+ }
86
+ if ( typeof item === 'string' || typeof item === 'number' ) {
87
+ return String( item );
88
+ }
89
+ return item.value ?? itemLabel( item );
90
+ }
91
+
92
+ function itemChildren( item ) {
93
+ return Array.isArray( item && item.children ) ? item.children : [];
94
+ }
95
+
96
+ function pathKey( path ) {
97
+ return Array.isArray( path ) ? path.map( Number ).join( ',' ) : '';
98
+ }
99
+
100
+ function sendWidgetEvent( type, node, extra = {} ) {
101
+ const state = node.__zuzuRendererState || rendererState();
102
+ if ( state.sendEvent ) {
103
+ state.sendEvent( {
104
+ windowId: state.windowId,
105
+ type,
106
+ widgetGuid: node.dataset.guid || null,
107
+ ...extra,
108
+ } );
109
+ return;
110
+ }
111
+ if ( !window.zuzuGui ) {
112
+ return;
113
+ }
114
+ window.zuzuGui.sendEvent( {
115
+ windowId: state.windowId,
116
+ type,
117
+ widgetGuid: node.dataset.guid || null,
118
+ ...extra,
119
+ } );
120
+ }
121
+
122
+ function sendThrottledWidgetEvent( type, node, extraFactory ) {
123
+ if ( node.__zuzuPendingEvent ) {
124
+ node.__zuzuPendingEvent.extraFactory = extraFactory;
125
+ return;
126
+ }
127
+ node.__zuzuPendingEvent = { extraFactory };
128
+ setTimeout( () => {
129
+ const pending = node.__zuzuPendingEvent;
130
+ node.__zuzuPendingEvent = null;
131
+ sendWidgetEvent( type, node, pending.extraFactory() );
132
+ }, 50 );
133
+ }
134
+
135
+ function applyCommonProps( element, snapshot ) {
136
+ const props = snapshot.props || {};
137
+ element.dataset.guid = snapshot.guid || '';
138
+ element.hidden = props.visible === false;
139
+ element.className = '';
140
+ if ( snapshot.classes && snapshot.classes.length > 0 ) {
141
+ element.classList.add( ...snapshot.classes );
142
+ }
143
+ if ( snapshot.id ) {
144
+ element.id = snapshot.id;
145
+ }
146
+ else {
147
+ element.removeAttribute( 'id' );
148
+ }
149
+ if ( props.disabled ) {
150
+ element.setAttribute( 'aria-disabled', 'true' );
151
+ if ( 'disabled' in element ) {
152
+ element.disabled = true;
153
+ }
154
+ }
155
+ else {
156
+ element.removeAttribute( 'aria-disabled' );
157
+ if ( 'disabled' in element ) {
158
+ element.disabled = false;
159
+ }
160
+ }
161
+ for ( const [ key, cssName ] of [
162
+ [ 'width', 'width' ],
163
+ [ 'height', 'height' ],
164
+ [ 'minwidth', 'minWidth' ],
165
+ [ 'minheight', 'minHeight' ],
166
+ [ 'maxwidth', 'maxWidth' ],
167
+ [ 'maxheight', 'maxHeight' ],
168
+ ] ) {
169
+ if ( props[key] != null && props[key] !== '' ) {
170
+ element.style[cssName] = `${Number( props[key] ) || 0}px`;
171
+ }
172
+ else {
173
+ element.style[cssName] = '';
174
+ }
175
+ }
176
+ }
177
+
178
+ function applyBoxStyle( element, snapshot ) {
179
+ const props = snapshot.props || {};
180
+ element.classList.add( snapshot.type === 'HBox' ? 'zuzu-hbox' : 'zuzu-vbox' );
181
+ element.style.display = 'flex';
182
+ element.style.flexDirection = snapshot.type === 'HBox' ? 'row' : 'column';
183
+ if ( props.gap != null ) {
184
+ element.style.gap = `${Number( props.gap ) || 0}px`;
185
+ }
186
+ if ( props.padding != null ) {
187
+ element.style.padding = `${Number( props.padding ) || 0}px`;
188
+ }
189
+ }
190
+
191
+ function applySpecificProps( element, snapshot ) {
192
+ const type = snapshot.type || 'Widget';
193
+ const props = snapshot.props || {};
194
+ if ( type === 'Window' ) {
195
+ return;
196
+ }
197
+ if ( type === 'Frame' ) {
198
+ let legend = element.querySelector( ':scope > legend' );
199
+ if ( !legend ) {
200
+ legend = rendererDocument().createElement( 'legend' );
201
+ element.prepend( legend );
202
+ }
203
+ legend.textContent = props.label || '';
204
+ element.classList.toggle( 'zuzu-collapsed', props.collapsed === true );
205
+ return;
206
+ }
207
+ if ( type === 'VBox' || type === 'HBox' ) {
208
+ applyBoxStyle( element, snapshot );
209
+ return;
210
+ }
211
+ if ( type === 'Label' ) {
212
+ element.textContent = props.text || '';
213
+ if ( props.for_id ) {
214
+ element.htmlFor = props.for_id;
215
+ }
216
+ return;
217
+ }
218
+ if ( type === 'Text' ) {
219
+ element.textContent = props.text || props.value || '';
220
+ element.style.whiteSpace = props.wrap === false ? 'pre' : 'pre-wrap';
221
+ return;
222
+ }
223
+ if ( type === 'RichText' ) {
224
+ element.innerHTML = props.value || '';
225
+ return;
226
+ }
227
+ if ( type === 'Image' ) {
228
+ element.src = props.src || '';
229
+ element.alt = props.alt || '';
230
+ element.style.objectFit = props.fit === 'stretch' ? 'fill' : ( props.fit || 'contain' );
231
+ return;
232
+ }
233
+ if ( type === 'Input' ) {
234
+ setInputValue( element, props.value || '' );
235
+ element.placeholder = props.placeholder || '';
236
+ element.readOnly = props.readonly === true;
237
+ element.required = props.required === true;
238
+ if ( 'type' in element ) {
239
+ element.type = props.password ? 'password' : 'text';
240
+ }
241
+ return;
242
+ }
243
+ if ( type === 'DatePicker' ) {
244
+ setInputValue( element, props.value || '' );
245
+ element.placeholder = 'YYYY-MM-DD';
246
+ element.pattern = '\\d{4}-\\d{2}-\\d{2}';
247
+ element.dataset.min = props.min || '';
248
+ element.dataset.max = props.max || '';
249
+ return;
250
+ }
251
+ if ( type === 'Checkbox' || type === 'Radio' ) {
252
+ const input = element.querySelector( 'input' );
253
+ const label = element.querySelector( 'span' );
254
+ input.checked = props.checked === true;
255
+ input.indeterminate = props.indeterminate === true;
256
+ input.value = props.value || '';
257
+ input.name = props.name || props.group || '';
258
+ label.textContent = props.label || '';
259
+ return;
260
+ }
261
+ if ( type === 'RadioGroup' ) {
262
+ const radios = element.querySelectorAll( ':scope input[type="radio"]' );
263
+ for ( const radio of radios ) {
264
+ radio.name = snapshot.guid || '';
265
+ }
266
+ return;
267
+ }
268
+ if ( type === 'Select' ) {
269
+ renderSelectOptions( element, props );
270
+ element.value = props.value == null ? '' : String( props.value );
271
+ element.multiple = props.multiple === true;
272
+ return;
273
+ }
274
+ if ( type === 'Separator' ) {
275
+ element.classList.toggle( 'zuzu-vertical-separator', props.orientation === 'vertical' );
276
+ return;
277
+ }
278
+ if ( type === 'Slider' ) {
279
+ setInputValue( element, props.value ?? 0 );
280
+ element.min = props.min ?? 0;
281
+ element.max = props.max ?? 100;
282
+ element.step = props.step ?? 1;
283
+ element.disabled = props.disabled === true || props.readonly === true;
284
+ return;
285
+ }
286
+ if ( type === 'Progress' ) {
287
+ element.value = Number( props.value ?? 0 );
288
+ element.max = Number( props.max ?? 100 );
289
+ element.classList.toggle( 'zuzu-indeterminate', props.indeterminate === true );
290
+ element.title = props.show_text ? `${element.value} / ${element.max}` : '';
291
+ return;
292
+ }
293
+ if ( type === 'Tabs' ) {
294
+ element.dataset.selected = props.selected || '';
295
+ for ( const child of Array.from( element.children ) ) {
296
+ if ( child.__zuzuGuiType === 'Tab' ) {
297
+ child.open = child.dataset.value === element.dataset.selected;
298
+ }
299
+ }
300
+ return;
301
+ }
302
+ if ( type === 'Tab' ) {
303
+ element.open = props.selected === true;
304
+ element.dataset.value = props.value || '';
305
+ element.querySelector( 'summary' ).textContent = props.title || props.value || '';
306
+ return;
307
+ }
308
+ if ( type === 'ListView' ) {
309
+ renderListView( element, props );
310
+ return;
311
+ }
312
+ if ( type === 'TreeView' ) {
313
+ renderTreeView( element, props );
314
+ return;
315
+ }
316
+ if ( type === 'Menu' ) {
317
+ let label = element.__zuzuMenuLabel;
318
+ if ( !label ) {
319
+ label = rendererDocument().createElement( 'span' );
320
+ label.className = 'zuzu-menu-label';
321
+ label.__zuzuRendererState = rendererState();
322
+ element.__zuzuMenuLabel = label;
323
+ element.insertBefore( label, element.children[0] || null );
324
+ }
325
+ label.textContent = props.text || '';
326
+ return;
327
+ }
328
+ if ( type === 'Button' || type === 'MenuItem' ) {
329
+ element.textContent = props.text || '';
330
+ }
331
+ }
332
+
333
+ function setInputValue( element, value ) {
334
+ const text = String( value ?? '' );
335
+ if ( element.value === text ) {
336
+ return;
337
+ }
338
+ const focused = rendererDocument().activeElement === element;
339
+ const selectionStart = focused ? element.selectionStart : null;
340
+ const selectionEnd = focused ? element.selectionEnd : null;
341
+ element.value = text;
342
+ if (
343
+ focused
344
+ && typeof element.setSelectionRange === 'function'
345
+ && selectionStart != null
346
+ && selectionEnd != null
347
+ ) {
348
+ const start = Math.min( selectionStart, text.length );
349
+ const end = Math.min( selectionEnd, text.length );
350
+ element.setSelectionRange( start, end );
351
+ }
352
+ }
353
+
354
+ function createElement( snapshot ) {
355
+ const type = snapshot.type || 'Widget';
356
+ let element;
357
+
358
+ if ( type === 'Window' ) {
359
+ element = rendererDocument().createElement( 'main' );
360
+ }
361
+ else if ( type === 'Frame' ) {
362
+ element = rendererDocument().createElement( 'fieldset' );
363
+ }
364
+ else if ( type === 'VBox' || type === 'HBox' ) {
365
+ element = rendererDocument().createElement( 'div' );
366
+ }
367
+ else if ( type === 'Label' ) {
368
+ element = rendererDocument().createElement( 'label' );
369
+ }
370
+ else if ( type === 'Text' || type === 'RichText' ) {
371
+ element = rendererDocument().createElement( 'div' );
372
+ element.addEventListener( 'click', () => {
373
+ sendWidgetEvent( 'click', element );
374
+ } );
375
+ }
376
+ else if ( type === 'Image' ) {
377
+ element = rendererDocument().createElement( 'img' );
378
+ element.addEventListener( 'click', () => {
379
+ sendWidgetEvent( 'click', element );
380
+ } );
381
+ }
382
+ else if ( type === 'Input' ) {
383
+ element = snapshot.props && snapshot.props.multiline
384
+ ? rendererDocument().createElement( 'textarea' )
385
+ : rendererDocument().createElement( 'input' );
386
+ element.addEventListener( 'input', () => {
387
+ sendWidgetEvent( 'input', element, { value: element.value } );
388
+ } );
389
+ element.addEventListener( 'change', () => {
390
+ sendWidgetEvent( 'change', element, { value: element.value } );
391
+ } );
392
+ }
393
+ else if ( type === 'DatePicker' ) {
394
+ element = rendererDocument().createElement( 'input' );
395
+ element.type = 'text';
396
+ element.inputMode = 'numeric';
397
+ element.autocomplete = 'off';
398
+ element.addEventListener( 'input', () => {
399
+ sendWidgetEvent( 'input', element, { value: element.value } );
400
+ } );
401
+ element.addEventListener( 'change', () => {
402
+ sendWidgetEvent( 'change', element, { value: element.value } );
403
+ } );
404
+ }
405
+ else if ( type === 'Checkbox' || type === 'Radio' ) {
406
+ element = rendererDocument().createElement( 'label' );
407
+ element.className = 'zuzu-check-control';
408
+ const input = rendererDocument().createElement( 'input' );
409
+ input.type = type === 'Radio' ? 'radio' : 'checkbox';
410
+ const label = rendererDocument().createElement( 'span' );
411
+ element.append( input, label );
412
+ input.addEventListener( 'change', () => {
413
+ const eventTarget = type === 'Radio' && element.dataset.parentGuid
414
+ ? element.dataset.parentGuid
415
+ : element.dataset.guid;
416
+ sendWidgetEvent( 'change', element, {
417
+ widgetGuid: eventTarget,
418
+ checked: input.checked,
419
+ value: input.value,
420
+ } );
421
+ } );
422
+ }
423
+ else if ( type === 'Select' ) {
424
+ element = rendererDocument().createElement( 'select' );
425
+ element.addEventListener( 'change', () => {
426
+ sendWidgetEvent( 'change', element, { value: element.value } );
427
+ } );
428
+ }
429
+ else if ( type === 'Button' || type === 'MenuItem' ) {
430
+ element = rendererDocument().createElement( 'button' );
431
+ element.type = 'button';
432
+ element.addEventListener( 'click', () => {
433
+ sendWidgetEvent( 'click', element );
434
+ } );
435
+ }
436
+ else if ( type === 'Menu' ) {
437
+ element = rendererDocument().createElement( 'nav' );
438
+ }
439
+ else if ( type === 'Separator' ) {
440
+ element = rendererDocument().createElement( 'hr' );
441
+ }
442
+ else if ( type === 'Slider' ) {
443
+ element = rendererDocument().createElement( 'input' );
444
+ element.type = 'range';
445
+ element.addEventListener( 'input', () => {
446
+ sendThrottledWidgetEvent( 'input', element, () => ( {
447
+ value: Number( element.value ),
448
+ } ) );
449
+ } );
450
+ element.addEventListener( 'change', () => {
451
+ sendWidgetEvent( 'change', element, { value: Number( element.value ) } );
452
+ } );
453
+ }
454
+ else if ( type === 'Progress' ) {
455
+ element = rendererDocument().createElement( 'progress' );
456
+ }
457
+ else if ( type === 'Tabs' ) {
458
+ element = rendererDocument().createElement( 'div' );
459
+ element.className = 'zuzu-tabs';
460
+ }
461
+ else if ( type === 'Tab' ) {
462
+ element = rendererDocument().createElement( 'details' );
463
+ const summary = rendererDocument().createElement( 'summary' );
464
+ element.appendChild( summary );
465
+ summary.addEventListener( 'click', (event) => {
466
+ event.preventDefault();
467
+ const parent = element.parentNode;
468
+ if ( parent ) {
469
+ for ( const child of Array.from( parent.children ) ) {
470
+ if ( child.__zuzuGuiType === 'Tab' ) {
471
+ child.open = child === element;
472
+ }
473
+ }
474
+ }
475
+ element.open = true;
476
+ sendWidgetEvent( 'select', element, {
477
+ widgetGuid: element.dataset.parentGuid || element.dataset.guid,
478
+ selected: element.dataset.value || '',
479
+ value: element.dataset.value || '',
480
+ } );
481
+ } );
482
+ }
483
+ else if ( type === 'ListView' || type === 'TreeView' ) {
484
+ element = rendererDocument().createElement( 'div' );
485
+ element.tabIndex = 0;
486
+ }
487
+ else {
488
+ element = rendererDocument().createElement( 'div' );
489
+ }
490
+
491
+ element.__zuzuRendererState = rendererState();
492
+ element.__zuzuGuiType = type;
493
+ element.__zuzuGuiSubType = type === 'Input' && snapshot.props && snapshot.props.multiline
494
+ ? 'multiline'
495
+ : '';
496
+ return element;
497
+ }
498
+
499
+ function renderNode( snapshot ) {
500
+ const element = createElement( snapshot );
501
+ applyCommonProps( element, snapshot );
502
+ applySpecificProps( element, snapshot );
503
+ rendererState().nodes.set( snapshot.guid, element );
504
+ for ( const child of snapshot.children || [] ) {
505
+ if (
506
+ snapshot.type === 'Window'
507
+ && child.type === 'Menu'
508
+ && !rendererState().renderMenus
509
+ ) {
510
+ continue;
511
+ }
512
+ const childElement = renderNode( child );
513
+ childElement.dataset.parentGuid = snapshot.guid || '';
514
+ if ( snapshot.type === 'RadioGroup' && child.type === 'Radio' ) {
515
+ const radio = childElement.querySelector( 'input[type="radio"]' );
516
+ if ( radio ) {
517
+ radio.name = snapshot.guid || '';
518
+ }
519
+ }
520
+ element.appendChild( childElement );
521
+ }
522
+ return element;
523
+ }
524
+
525
+ function renderSelectOptions( element, props ) {
526
+ element.replaceChildren();
527
+ for ( const item of props.options || [] ) {
528
+ const option = rendererDocument().createElement( 'option' );
529
+ option.textContent = itemLabel( item );
530
+ option.value = String( itemValue( item ) );
531
+ option.disabled = item && item.disabled === true;
532
+ element.appendChild( option );
533
+ }
534
+ }
535
+
536
+ function renderListView( element, props ) {
537
+ element.classList.add( 'zuzu-listview' );
538
+ element.replaceChildren();
539
+ const list = rendererDocument().createElement( 'ul' );
540
+ const selected = props.selected_index == null ? null : Number( props.selected_index );
541
+ ( props.items || [] ).forEach( (item, index) => {
542
+ const row = rendererDocument().createElement( 'li' );
543
+ row.textContent = itemLabel( item );
544
+ row.dataset.index = String( index );
545
+ row.classList.toggle( 'selected', index === selected );
546
+ row.addEventListener( 'click', () => {
547
+ markListSelected( element, index );
548
+ sendWidgetEvent( 'select', element, { selected_index: index } );
549
+ } );
550
+ row.addEventListener( 'dblclick', () => {
551
+ markListSelected( element, index );
552
+ sendWidgetEvent( 'activate', element, { selected_index: index } );
553
+ } );
554
+ list.appendChild( row );
555
+ } );
556
+ element.appendChild( list );
557
+ }
558
+
559
+ function markListSelected( element, index ) {
560
+ for ( const row of element.querySelectorAll( 'li' ) ) {
561
+ row.classList.toggle( 'selected', Number( row.dataset.index ) === Number( index ) );
562
+ }
563
+ }
564
+
565
+ function renderTreeView( element, props ) {
566
+ element.classList.add( 'zuzu-treeview' );
567
+ element.replaceChildren();
568
+ const selected = pathKey( props.selected_path || [] );
569
+ const expanded = new Set( ( props.expanded_paths || [] ).map( pathKey ) );
570
+ element.appendChild( renderTreeItems( props.items || [], [], selected, expanded, element ) );
571
+ }
572
+
573
+ function renderTreeItems( items, prefix, selected, expanded, owner ) {
574
+ const list = rendererDocument().createElement( 'ul' );
575
+ items.forEach( (item, index) => {
576
+ const path = [ ...prefix, index ];
577
+ const key = pathKey( path );
578
+ const children = itemChildren( item );
579
+ const row = rendererDocument().createElement( 'li' );
580
+ if ( children.length > 0 ) {
581
+ const details = rendererDocument().createElement( 'details' );
582
+ details.open = expanded.has( key );
583
+ const summary = rendererDocument().createElement( 'summary' );
584
+ summary.textContent = itemLabel( item );
585
+ summary.dataset.path = key;
586
+ summary.classList.toggle( 'selected', key === selected );
587
+ summary.addEventListener( 'click', () => {
588
+ markTreeSelected( owner, key );
589
+ sendWidgetEvent( 'select', owner, { selected_path: path } );
590
+ } );
591
+ summary.addEventListener( 'dblclick', () => {
592
+ markTreeSelected( owner, key );
593
+ sendWidgetEvent( 'activate', owner, { selected_path: path } );
594
+ } );
595
+ details.addEventListener( 'toggle', () => {
596
+ sendWidgetEvent( details.open ? 'expand' : 'collapse', owner, {
597
+ selected_path: path,
598
+ } );
599
+ } );
600
+ details.append( summary, renderTreeItems( children, path, selected, expanded, owner ) );
601
+ row.appendChild( details );
602
+ }
603
+ else {
604
+ const button = rendererDocument().createElement( 'button' );
605
+ button.type = 'button';
606
+ button.textContent = itemLabel( item );
607
+ button.dataset.path = key;
608
+ button.classList.toggle( 'selected', key === selected );
609
+ button.addEventListener( 'click', () => {
610
+ markTreeSelected( owner, key );
611
+ sendWidgetEvent( 'select', owner, { selected_path: path } );
612
+ } );
613
+ button.addEventListener( 'dblclick', () => {
614
+ markTreeSelected( owner, key );
615
+ sendWidgetEvent( 'activate', owner, { selected_path: path } );
616
+ } );
617
+ row.appendChild( button );
618
+ }
619
+ list.appendChild( row );
620
+ } );
621
+ return list;
622
+ }
623
+
624
+ function markTreeSelected( element, key ) {
625
+ for ( const node of element.querySelectorAll( '[data-path]' ) ) {
626
+ node.classList.toggle( 'selected', node.dataset.path === key );
627
+ }
628
+ }
629
+
630
+ function replaceNode( snapshot ) {
631
+ const old = rendererState().nodes.get( snapshot.guid );
632
+ if ( !old ) {
633
+ return;
634
+ }
635
+ const parent = old.parentNode;
636
+ const next = renderNode( snapshot );
637
+ forgetNode( old );
638
+ parent.replaceChild( next, old );
639
+ }
640
+
641
+ function forgetNode( node ) {
642
+ const guid = node && node.dataset ? node.dataset.guid : null;
643
+ if ( guid ) {
644
+ rendererState().nodes.delete( guid );
645
+ }
646
+ for ( const child of Array.from( node.children || [] ) ) {
647
+ forgetNode( child );
648
+ }
649
+ }
650
+
651
+ function updateNode( snapshot ) {
652
+ const element = rendererState().nodes.get( snapshot.guid );
653
+ if ( !element ) {
654
+ return;
655
+ }
656
+ if ( element.__zuzuGuiType !== ( snapshot.type || 'Widget' ) ) {
657
+ replaceNode( snapshot );
658
+ return;
659
+ }
660
+ if (
661
+ element.__zuzuGuiType === 'Input'
662
+ && element.__zuzuGuiSubType !== (
663
+ snapshot.props && snapshot.props.multiline ? 'multiline' : ''
664
+ )
665
+ ) {
666
+ replaceNode( snapshot );
667
+ return;
668
+ }
669
+ applyCommonProps( element, snapshot );
670
+ applySpecificProps( element, snapshot );
671
+ }
672
+
673
+ function createZuzuGuiDomRenderer( options = {} ) {
674
+ installSafeDefineProperties();
675
+ const doc = options.document
676
+ || ( options.root && options.root.ownerDocument )
677
+ || document;
678
+ const root = options.root
679
+ || doc.getElementById( options.rootId || 'app' );
680
+ if ( !root ) {
681
+ throw new Error( 'Zuzu GUI DOM renderer requires a root element' );
682
+ }
683
+ const state = {
684
+ document: doc,
685
+ windowId: null,
686
+ nodes: new Map(),
687
+ root,
688
+ sendEvent: typeof options.sendEvent === 'function'
689
+ ? options.sendEvent
690
+ : null,
691
+ renderMenus: options.renderMenus === true,
692
+ };
693
+ function withState( fn ) {
694
+ const previous = activeState;
695
+ activeState = state;
696
+ try {
697
+ return fn();
698
+ }
699
+ finally {
700
+ activeState = previous;
701
+ }
702
+ }
703
+ return {
704
+ render( payload = {} ) {
705
+ return withState( () => {
706
+ state.windowId = payload.windowId;
707
+ state.nodes.clear();
708
+ root.replaceChildren( renderNode( payload.snapshot ) );
709
+ } );
710
+ },
711
+ create( payload = {} ) {
712
+ return withState( () => {
713
+ if (
714
+ payload.snapshot
715
+ && payload.snapshot.type === 'Menu'
716
+ && !state.renderMenus
717
+ ) {
718
+ return;
719
+ }
720
+ const parent = state.nodes.get( payload.parentGuid );
721
+ if ( !parent ) {
722
+ return;
723
+ }
724
+ const node = renderNode( payload.snapshot );
725
+ const index = Number.isInteger( payload.index ) ? payload.index : null;
726
+ if ( index == null || index >= parent.children.length ) {
727
+ parent.appendChild( node );
728
+ }
729
+ else {
730
+ parent.insertBefore( node, parent.children[index] );
731
+ }
732
+ } );
733
+ },
734
+ update( payload = {} ) {
735
+ return withState( () => {
736
+ if ( !payload.snapshot ) {
737
+ return;
738
+ }
739
+ if (
740
+ (
741
+ payload.snapshot.type === 'Menu'
742
+ || payload.snapshot.type === 'MenuItem'
743
+ )
744
+ && !state.renderMenus
745
+ ) {
746
+ return;
747
+ }
748
+ updateNode( payload.snapshot );
749
+ } );
750
+ },
751
+ destroy( payload = {} ) {
752
+ return withState( () => {
753
+ const node = state.nodes.get( payload.widgetGuid );
754
+ if ( node && node.parentNode ) {
755
+ node.parentNode.removeChild( node );
756
+ }
757
+ if ( node ) {
758
+ forgetNode( node );
759
+ }
760
+ } );
761
+ },
762
+ nodeForGuid( guid ) {
763
+ return state.nodes.get( guid ) || null;
764
+ },
765
+ };
766
+ }
767
+
768
+ if ( typeof module !== 'undefined' && module.exports ) {
769
+ module.exports = {
770
+ createZuzuGuiDomRenderer,
771
+ };
772
+ }
773
+
774
+ if ( typeof window !== 'undefined' ) {
775
+ window.ZuzuGuiDomRenderer = {
776
+ createZuzuGuiDomRenderer,
777
+ };
778
+ }