@zeyos/zx 2.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,74 @@
1
+ # Changelog
2
+
3
+ Notable changes to `@zeyos/zx`. The format follows [Keep a Changelog](https://keepachangelog.com),
4
+ and the project follows [semantic versioning](https://semver.org).
5
+
6
+ Releases before this file existed are summarised from the git history; `git log v2.0.1..v2.0.2`
7
+ and friends remain the complete record.
8
+
9
+ ## Unreleased
10
+
11
+ ## 2.1.0 — 2026-08-19
12
+
13
+ ### Added
14
+
15
+ - **`spinner()`, `ProgressBar`, `InlineLoading`** — three shapes of "wait". A ring for an unknown
16
+ duration, a determinate track for a known share, and a status line that resolves in place
17
+ ("Saving…" becoming "Saved"). Previously the only loading affordance was `Table.setLoading()`,
18
+ which belongs to the table and cannot be used anywhere else.
19
+ - **`skeleton()`, `skeletonText()`, `skeletonTable()`** — placeholders shaped like the content that
20
+ is coming, so the layout does not jump when it arrives. All `aria-hidden`; put `aria-busy="true"`
21
+ on the region being filled.
22
+ - **`stack()`, `grid()`, `aspect()`** and the `.zx-stack` / `.zx-grid` / `.zx-aspect` classes —
23
+ the spacing and grid primitives under `Panel` and `SplitView`. `grid()` reflows on its own
24
+ container width rather than the viewport, so it behaves identically inside a split pane or a
25
+ modal.
26
+ - **`breakpoints`, `breakpointOf()`, `matchBreakpoint()`, `onBreakpoint()`** — named width bands
27
+ for the decisions only script can make. `onBreakpoint()` observes an element as readily as the
28
+ window.
29
+ - **`Slider`** and the `slider` field type — a bounded numeric value set by dragging, on a native
30
+ range input, with optional marks, bounds, and a number box. `stepPrecision()` is exported
31
+ alongside it.
32
+ - **`ContextMenu`** — right-click menu for a region or, through `selector`, per row from a single
33
+ instance. Reachable from the keyboard with the Menu key and Shift+F10.
34
+ - **`copyButton()` and `CopyInput`** — copying a value, and confirming it; a refused clipboard
35
+ write reports `false` rather than claiming success.
36
+ - **`truncate()` and `isTruncated()`**, plus the `.zx-truncate` class — line clamping that exposes
37
+ the full value as a `title` only while the text is actually cut off.
38
+ - `copy` added to the built-in icon set.
39
+ - `CHANGELOG.md`, included in the published package.
40
+
41
+ ### Changed
42
+
43
+ - The landing page routes by intent — start something new, see what is there, or move an old
44
+ screen across — states how to install the package, lists the entry points, and renders the
45
+ newest changelog entry so a release updates it too. The footer, shared by every page, carries
46
+ grouped links plus the version and the build date.
47
+
48
+ ## 2.0.2 — 2026-08-19
49
+
50
+ ### Fixed
51
+
52
+ - The base stylesheet now replaces the browser's own focus outline instead of stacking on it, so a
53
+ focused control no longer shows a blue border inside the accent ring.
54
+ - Tabbox variants match the approved draft, and the tab clears the inherited control radius.
55
+ - Every deploy is served from a revision-stamped path, and the imports the markup cannot see are
56
+ fingerprinted with it — working around a CDN that rewrites cache headers.
57
+
58
+ ## 2.0.1 — 2026-08-19
59
+
60
+ ### Added
61
+
62
+ - Tabbox gained four square-cornered variants (`divided`, `bracket`, `line`, `segmented`) with
63
+ `--zx-tabbox-radius` as an escape hatch. **The default moved from `line` to `divided`**, so a
64
+ Tabbox with no explicit `variant` changes appearance.
65
+
66
+ ### Changed
67
+
68
+ - The documentation is one scrolling page per component, with the source folded under each example.
69
+
70
+ ## 2.0.0 — 2026-08-18
71
+
72
+ First stable release: the dependency-free, vanilla-JavaScript successor to the MooTools-era `gx`
73
+ library. Components, design tokens with light/dark and cozy/compact theming, the `zx-zeyos`
74
+ schema-driven binding, and an opt-in `gx` compatibility layer.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ZeyOS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/MIGRATION.md ADDED
@@ -0,0 +1,598 @@
1
+ # Migrating from `gx` to Zx
2
+
3
+ Zx replaces the MooTools-era `gx` UI libraries with dependency-free ES2022 modules. The
4
+ compatibility bundle lets mapped legacy code keep running while classes are migrated one at a
5
+ time. Compatibility constructors warn once per legacy class so remaining call sites are visible.
6
+
7
+ ## Deployment script swap
8
+
9
+ For deployments that serve the old libraries directly from
10
+ `net.zeyon.lib.gx-3000/assets/`, copy `dist/zx.global.js`, `dist/zx-compat.global.js`, and
11
+ `dist/zx.css` into that asset directory. Replace:
12
+
13
+ ```html
14
+ <link rel="stylesheet" href="/net.zeyon.lib.gx-3000/assets/gx.css">
15
+ <script src="/net.zeyon.lib.gx-3000/assets/gx-core.js"></script>
16
+ <script src="/net.zeyon.lib.gx-3000/assets/gx-zeyos.js"></script>
17
+ ```
18
+
19
+ with this order:
20
+
21
+ ```html
22
+ <link rel="stylesheet" href="/net.zeyon.lib.gx-3000/assets/zx.css">
23
+ <script src="/net.zeyon.lib.gx-3000/assets/zx.global.js"></script>
24
+ <script src="/net.zeyon.lib.gx-3000/assets/zx-compat.global.js"></script>
25
+ ```
26
+
27
+ The compatibility entry exposes both `window.zx` and `window.gx`. Element
28
+ `store`/`retrieve`/`eliminate` is installed automatically. If the application uses `__()`, `_()`,
29
+ `String.prototype.htmlSpecialChars`, or `Array.prototype.findBy`, opt in after both scripts load:
30
+
31
+ ```js
32
+ gx.compat.installGlobals();
33
+ ```
34
+
35
+ Do not call this for newly migrated code; import `h`, `htmlEscape`, and the i18n helpers directly.
36
+
37
+ ## Class-by-class replacements
38
+
39
+ The snippets show the normal direct-Zx replacement. All strings supplied as component content are
40
+ inserted as text; pass a DOM node when structured content is needed.
41
+
42
+ ### `gx.zeyos.Toggle` → `zx.Toggle`
43
+
44
+ ```js
45
+ // gx
46
+ const control = new gx.zeyos.Toggle(host, { on: true, value: 'enabled' });
47
+ control.addEvent('uncheck', () => save(false));
48
+
49
+ // Zx
50
+ const control = new zx.Toggle(host, { checked: true, value: 'enabled' });
51
+ control.on('change', (event) => save(event.detail.checked));
52
+ ```
53
+
54
+ Use `get()`, `getValue()`, `set(boolean)`, and `toggle()` in place of
55
+ `getState()`, `setChecked()`, and `setUnchecked()`.
56
+
57
+ ### `gx.zeyos.Msgbox` → `zx.Message`
58
+
59
+ ```js
60
+ // gx
61
+ const messages = new gx.zeyos.Msgbox();
62
+ messages.show('Saved', 's_msg_32_success');
63
+
64
+ // Zx
65
+ const messages = new zx.Message(null, { timeout: 0 });
66
+ document.body.append(messages.el);
67
+ const handle = messages.show('Saved', { kind: 'success' });
68
+ ```
69
+
70
+ Call `handle.close()` instead of `hide()`.
71
+
72
+ ### `gx.bootstrap.Message` → `zx.Message`
73
+
74
+ ```js
75
+ // gx
76
+ const messages = new gx.bootstrap.Message(host);
77
+ messages.addMessage('Saved', 'success', true);
78
+ messages.showStatus(40, 'Uploading');
79
+
80
+ // Zx
81
+ const messages = new zx.Message(host);
82
+ messages.show('Saved', { kind: 'success', closable: true });
83
+ const progress = zx.Message.progress('Uploading');
84
+ progress.update(40);
85
+ ```
86
+
87
+ Use `progress.done()` or `progress.fail(message)` instead of `hideStatus()`.
88
+
89
+ ### `gx.zeyos.Popup` and `gx.bootstrap.Popup` → `zx.Dialog`
90
+
91
+ ```js
92
+ // gx (either Popup class)
93
+ const popup = new gx.zeyos.Popup({ width: 480, content: 'Details', overlayDismiss: true });
94
+ popup.show();
95
+
96
+ // Zx
97
+ const popup = new zx.Dialog(null, { size: 480, content: 'Details', lightDismiss: true });
98
+ popup.open();
99
+ ```
100
+
101
+ Replace `show`/`hide` events and methods with `open`/`close`. Use CSS for custom positioning;
102
+ legacy `setPosition()` is only a warning-producing compatibility no-op.
103
+
104
+ ### `gx.bootstrap.PopupAlert` → `zx.Dialog.alert`
105
+
106
+ ```js
107
+ // gx
108
+ await gx.bootstrap.PopupAlert('Saved', 'The record was saved.');
109
+
110
+ // Zx
111
+ await zx.Dialog.alert({ title: 'Saved', message: 'The record was saved.' });
112
+ ```
113
+
114
+ ### `gx.bootstrap.PopupConfirm` and `PopupConfirmCanceled` → `zx.Dialog.confirm`
115
+
116
+ ```js
117
+ // gx
118
+ const result = await gx.bootstrap.PopupConfirm('Delete', 'Delete this record?');
119
+ if (result !== gx.bootstrap.PopupConfirmCanceled) removeRecord();
120
+
121
+ // Zx
122
+ if (await zx.Dialog.confirm({ title: 'Delete', message: 'Delete this record?', danger: true })) {
123
+ removeRecord();
124
+ }
125
+ ```
126
+
127
+ ### `gx.zeyos.Dialog` → `zx.Dialog` views
128
+
129
+ ```js
130
+ // gx
131
+ const dialog = new gx.zeyos.Dialog(null, { title: 'Wizard' });
132
+ dialog.addFrame('review', { title: 'Review', content: reviewNode }, true);
133
+ dialog.openFrame('review');
134
+
135
+ // Zx
136
+ const dialog = new zx.Dialog(null, { title: 'Wizard' });
137
+ dialog.addView('review', { title: 'Review', content: reviewNode });
138
+ dialog.showView('review');
139
+ dialog.open();
140
+ ```
141
+
142
+ Replace `addSubmitFrame`, `addFormFrame`, and `addSuccessFrame` with explicit views, `Form`
143
+ instances, and dialog button actions. Read form data with `form.getValues()`.
144
+
145
+ ### `gx.zeyos.Dropdown` → `zx.MenuButton`
146
+
147
+ ```js
148
+ // gx
149
+ const menu = new gx.zeyos.Dropdown(host, { items: { edit: 'Edit', remove: 'Remove' } });
150
+ menu.addEvent('change', (value) => run(value));
151
+
152
+ // Zx
153
+ const menu = new zx.MenuButton(null, {
154
+ label: 'Actions',
155
+ items: [{ value: 'edit', label: 'Edit' }, { value: 'remove', label: 'Remove' }],
156
+ onselect: (event) => run(event.detail.value)
157
+ });
158
+ host.append(menu.el);
159
+ ```
160
+
161
+ ### `gx.bootstrap.MenuButton` → `zx.MenuButton`
162
+
163
+ ```js
164
+ // gx
165
+ const menu = new gx.bootstrap.MenuButton(host, { label: 'Actions' });
166
+ menu.add('Edit').divider().add('Remove');
167
+
168
+ // Zx
169
+ const menu = new zx.MenuButton(host, {
170
+ label: 'Actions', items: [{ label: 'Edit', value: 'edit' }, '-', { label: 'Remove', value: 'remove' }]
171
+ });
172
+ ```
173
+
174
+ ### `gx.zeyos.Select` and `gx.bootstrap.Select` → `zx.Select`
175
+
176
+ ```js
177
+ // gx (either namespace)
178
+ const select = new gx.zeyos.Select(host, {
179
+ data: rows, elementIndex: 'ID', elementLabel: 'name', allowEmpty: true
180
+ });
181
+ select.addEvent('select', (item) => save(item.ID));
182
+
183
+ // Zx
184
+ const select = new zx.Select(host, {
185
+ items: rows, valueKey: 'ID', labelKey: 'name', clearable: true,
186
+ onchange: (event) => save(event.detail.value)
187
+ });
188
+ ```
189
+
190
+ Use `value`/`selected`, `set()`, `setItems()`, and `reset()`. A cleared selection is the same
191
+ `change` event with `{value: null, item: null}` rather than a separate `noselect` event.
192
+
193
+ ### `gx.zeyos.SelectFilter` and `gx.bootstrap.SelectFilter` → local `zx.Select`
194
+
195
+ ```js
196
+ // gx
197
+ const select = new gx.zeyos.SelectFilter(host, { data: rows, searchfields: ['name', 'code'] });
198
+
199
+ // Zx
200
+ const select = new zx.Select(host, { items: rows, filter: 'local', searchKeys: ['name', 'code'] });
201
+ ```
202
+
203
+ ### `gx.zeyos.SelectDyn` and `gx.bootstrap.SelectDyn` → async `zx.Select`
204
+
205
+ ```js
206
+ // gx
207
+ const select = new gx.zeyos.SelectDyn(host, { url: '/people', queryParam: 'query' });
208
+
209
+ // Zx
210
+ const http = new zx.Http();
211
+ const select = new zx.Select(host, {
212
+ filter: (query) => http.get('/people', { query }),
213
+ onloaded: (event) => console.log(event.detail.items)
214
+ });
215
+ ```
216
+
217
+ ### `gx.bootstrap.SelectDynREST` → `zx.Select` + `zx.zeyosService`
218
+
219
+ ```js
220
+ // gx
221
+ const select = new gx.bootstrap.SelectDynREST(host, { entity: 'contacts', limit: 20 });
222
+
223
+ // Zx
224
+ const contacts = zx.zeyosService('contacts');
225
+ const select = new zx.Select(host, {
226
+ filter: (query) => contacts.get('', { search: query, limit: 20 })
227
+ });
228
+ ```
229
+
230
+ ### `gx.zeyos.SelectPrio` and `gx.bootstrap.SelectPrio` → `zx.Select.priority`
231
+
232
+ ```js
233
+ // gx
234
+ const priority = new gx.zeyos.SelectPrio(host, { value: 2 });
235
+
236
+ // Zx
237
+ const priority = zx.Select.priority(host, { value: 2 });
238
+ ```
239
+
240
+ ### `gx.zeyos.Checklist` and `gx.bootstrap.Checklist` → `zx.Checklist`
241
+
242
+ ```js
243
+ // gx (either namespace)
244
+ const list = new gx.zeyos.Checklist(host, { data: rows, listValue: 'ID', listFormat: 'name' });
245
+ list.setValues([1, 2]);
246
+
247
+ // Zx
248
+ const list = new zx.Checklist(host, { items: rows, valueKey: 'ID', labelKey: 'name' });
249
+ list.setValues([1, 2]);
250
+ ```
251
+
252
+ For remote data pass `load: () => Promise<items>` and listen for `loaded`.
253
+
254
+ ### `gx.zeyos.Permission` → `zx.Permission`
255
+
256
+ ```js
257
+ // gx
258
+ const permission = new gx.zeyos.Permission(host, { value: true, groups });
259
+
260
+ // Zx
261
+ const permission = new zx.Permission(host, { value: 'public', groups });
262
+ permission.on('change', (event) => save(event.detail.value));
263
+ ```
264
+
265
+ ### `gx.zeyos.Table` and `gx.bootstrap.Table` → `zx.Table`
266
+
267
+ ```js
268
+ // gx (either namespace)
269
+ const table = new gx.zeyos.Table(host, {
270
+ cols: [{ id: 'name', label: 'Name', filterable: true }],
271
+ structure: (row) => [row.name], data: rows
272
+ });
273
+
274
+ // Zx
275
+ const table = new zx.Table(host, {
276
+ columns: [{ id: 'name', label: 'Name', sortable: true }],
277
+ data: rows, rowId: 'ID'
278
+ });
279
+ ```
280
+
281
+ Move `structure()` work into per-column `render(row, index)`. Replace `filter` events with `sort`
282
+ and `click`/`dblclick` with `rowclick`/`rowdblclick`; all payloads are in `event.detail`.
283
+
284
+ ### `gx.ui.SimpleTable` → `zx.Table`
285
+
286
+ ```js
287
+ // gx
288
+ const table = new gx.ui.SimpleTable({ cols: ['Name'] });
289
+ table.addRow({ ID: 1, name: 'Ada' });
290
+
291
+ // Zx
292
+ const table = new zx.Table(null, { columns: [{ id: 'name', label: 'Name' }], rowId: 'ID' });
293
+ table.addData([{ ID: 1, name: 'Ada' }]);
294
+ ```
295
+
296
+ Use `setData`, `addData`, `updateRow`, `removeRow`, and `getData` for row operations.
297
+
298
+ ### `gx.zeyos.Tabbox` and `gx.bootstrap.Tabbox` → `zx.Tabbox`
299
+
300
+ ```js
301
+ // gx (either namespace; show is one-based)
302
+ const tabs = new gx.zeyos.Tabbox(host, { frames, show: 2 });
303
+ tabs.openTab('history');
304
+
305
+ // Zx
306
+ const tabs = new zx.Tabbox(host, { tabs, active: 'history' });
307
+ tabs.openTab('history');
308
+ ```
309
+
310
+ Rename `frames` to `tabs`; each entry keeps `name`, `title`, and `content`. Use `setBadge()` in
311
+ place of `setHighlight()`.
312
+
313
+ ### `gx.bootstrap.NavigationBar` → `zx.NavigationBar`
314
+
315
+ ```js
316
+ // gx
317
+ const navigation = new gx.bootstrap.NavigationBar(host, { title: 'ZeyOS', frames, buttons });
318
+
319
+ // Zx
320
+ const navigation = new zx.NavigationBar(host, { title: 'ZeyOS', items: frames, actions: buttons });
321
+ ```
322
+
323
+ ### `gx.zeyos.Panel` → `zx.Panel`
324
+
325
+ ```js
326
+ // gx
327
+ const panel = new gx.zeyos.Panel(host, 'Details', contentNode, true);
328
+
329
+ // Zx
330
+ const panel = new zx.Panel(host, { title: 'Details', content: contentNode, open: true });
331
+ ```
332
+
333
+ ### `gx.zeyos.MasterPanel` → `zx.MasterPanel`
334
+
335
+ ```js
336
+ // gx
337
+ const panel = new gx.zeyos.MasterPanel(host, 'Contacts', contentNode, buttons);
338
+
339
+ // Zx
340
+ const panel = new zx.MasterPanel(host, { title: 'Contacts', content: contentNode, buttons });
341
+ ```
342
+
343
+ ### `gx.zeyos.Groupbox` → `zx.Groupbox`
344
+
345
+ ```js
346
+ // gx
347
+ const group = new gx.zeyos.Groupbox(host, { title: 'Advanced', open: false });
348
+
349
+ // Zx
350
+ const group = new zx.Groupbox(host, { title: 'Advanced', open: false });
351
+ group.open();
352
+ ```
353
+
354
+ ### `gx.zeyos.Search` → `zx.Search`
355
+
356
+ ```js
357
+ // gx
358
+ const search = new gx.zeyos.Search();
359
+ search.addEvent('keypress', () => load(search.get()));
360
+
361
+ // Zx
362
+ const search = new zx.Search(null, { onsubmit: (event) => load(event.detail.value) });
363
+ host.append(search.el);
364
+ ```
365
+
366
+ ### `gx.zeyos.Datebox` → `zx.Datebox` or `zx.DateTimeBox`
367
+
368
+ ```js
369
+ // gx
370
+ const date = new gx.zeyos.Datebox(host, { timestamp, unit: 'seconds', format: ['d', '.', 'M', '.', 'y'] });
371
+
372
+ // Zx
373
+ const date = new zx.Datebox(host, { value: timestamp, format: '%d.%m.%Y' });
374
+ const timestampSeconds = date.get('seconds');
375
+ ```
376
+
377
+ Use `zx.DateTimeBox(host, options)` when the legacy format included hours/minutes.
378
+
379
+ ### `gx.zeyos.DatePicker` → `zx.DatePicker`
380
+
381
+ ```js
382
+ // gx
383
+ const picker = new gx.zeyos.DatePicker(host, { date: initial, weeknumbers: true });
384
+
385
+ // Zx
386
+ const picker = new zx.DatePicker(host, { value: initial, showWeekNumbers: true });
387
+ ```
388
+
389
+ ### `gx.zeyos.MonthPicker` → `zx.MonthPicker`
390
+
391
+ ```js
392
+ // gx
393
+ const picker = new gx.zeyos.MonthPicker(host, { date: initial });
394
+
395
+ // Zx
396
+ const picker = new zx.MonthPicker(host, { value: initial });
397
+ ```
398
+
399
+ ### `gx.zeyos.TimePicker` → `zx.TimePicker`
400
+
401
+ ```js
402
+ // gx
403
+ const picker = new gx.zeyos.TimePicker(host, { date: initial, format: '%H:%M:%S' });
404
+
405
+ // Zx
406
+ const picker = new zx.TimePicker(host, { value: initial, seconds: true });
407
+ ```
408
+
409
+ Direct picker `change` events carry `{date}` or `{time}`. Use `Date#getTime()` or the exported
410
+ date formatting helpers when a timestamp or formatted string is required.
411
+
412
+ ### `gx.ui.Timebox`, `gx.zeyos.Timebox`, and `gx.bootstrap.Timebox` → `zx.Timebox`
413
+
414
+ ```js
415
+ // gx (any namespace)
416
+ const duration = new gx.zeyos.Timebox(host, { time: -90, unit: 'minutes', prefix: true });
417
+
418
+ // Zx
419
+ const duration = new zx.Timebox(host, { value: -90, unit: 'minutes', signed: true });
420
+ ```
421
+
422
+ Use `get(unit)`, `set(value, unit)`, `enable()`, and `disable()`.
423
+
424
+ ### `gx.bootstrap.CheckButton` → `zx.CheckButton`
425
+
426
+ ```js
427
+ // gx
428
+ const check = new gx.bootstrap.CheckButton(host, { label: ['On', 'Off'], value: true });
429
+
430
+ // Zx
431
+ const check = new zx.CheckButton(host, { label: ['On', 'Off'], checked: true });
432
+ check.on('change', (event) => save(event.detail.checked));
433
+ ```
434
+
435
+ ### `gx.bootstrap.Form` → `zx.Form`
436
+
437
+ ```js
438
+ // gx
439
+ const form = new gx.bootstrap.Form(host, { horizontal: true, fields: legacyFields });
440
+
441
+ // Zx
442
+ const form = new zx.Form(host, {
443
+ fieldsets: [{ fields: modernFields }],
444
+ actions: [{ id: 'save', label: 'Save', type: 'submit', kind: 'primary' }]
445
+ });
446
+ ```
447
+
448
+ Use `getValues`, `setValues`, `getField`, `setHighlights`, `reset`, and the preventable `submit`
449
+ event.
450
+
451
+ ### `gx.bootstrap.Fieldset` → `zx.Fieldset`
452
+
453
+ ```js
454
+ // gx
455
+ const fields = new gx.bootstrap.Fieldset(host, { title: 'Contact', fields: legacyFields });
456
+
457
+ // Zx
458
+ const fields = new zx.Fieldset(host, { title: 'Contact', columns: 2, fields: modernFields });
459
+ ```
460
+
461
+ ### `gx.bootstrap.Field` → `zx.Field`
462
+
463
+ ```js
464
+ // gx
465
+ const field = new gx.bootstrap.Field({ type: 'text', label: 'Name', value: 'Ada' });
466
+ host.append(field.toElement());
467
+
468
+ // Zx
469
+ const field = new zx.Field(host, { id: 'name', type: 'text', label: 'Name', value: 'Ada' });
470
+ ```
471
+
472
+ Legacy `gxselect`, date/month/time widgets, option lists, and custom HTML fields map to registered
473
+ Zx field adapters. Highlights use `danger`, `warning`, or `success`.
474
+
475
+ ### `gx.bootstrap.ValueList` → `zx.ValueList`
476
+
477
+ ```js
478
+ // gx
479
+ const values = new gx.bootstrap.ValueList(host, { deletable: true });
480
+ values.addValue('one');
481
+
482
+ // Zx
483
+ const values = new zx.ValueList(host, { deletable: true, sortable: true });
484
+ values.addValue('one');
485
+ ```
486
+
487
+ ### `gx.bootstrap.MultiValueEditor` → `zx.MultiValueEditor`
488
+
489
+ ```js
490
+ // gx (JSON string)
491
+ const editor = new gx.bootstrap.MultiValueEditor(host);
492
+ editor.setValue('["one","two"]');
493
+
494
+ // Zx (array)
495
+ const editor = new zx.MultiValueEditor(host);
496
+ editor.setValues(['one', 'two']);
497
+ ```
498
+
499
+ The compatibility wrapper deliberately preserves JSON-string `getValue()`/`setValue()` only for
500
+ unmigrated callers.
501
+
502
+ ### `gx.bootstrap.FieldUpload` → `zx.FieldUpload`
503
+
504
+ ```js
505
+ // gx
506
+ const upload = new gx.bootstrap.FieldUpload({ uploadurl: '/upload', inputname: 'document' });
507
+ host.append(upload.toElement());
508
+
509
+ // Zx
510
+ const upload = new zx.FieldUpload(host, { url: '/upload', paramName: 'document' });
511
+ ```
512
+
513
+ Use `params`, `accept`, `maxSize`, `multiple`, and the `select`, `progress`, `success`, `error`, and
514
+ `abort` events.
515
+
516
+ ### `gx.bootstrap.DataFilter` → `zx.DataFilter`
517
+
518
+ ```js
519
+ // gx
520
+ const filter = new gx.bootstrap.DataFilter(host);
521
+ filter.addFulltextFilter('query', ['name', 'city']).initData(rows).doFilter();
522
+
523
+ // Zx
524
+ const filter = new zx.DataFilter(host, {
525
+ data: rows,
526
+ filters: [{ type: 'text', id: 'query', label: 'Search', fields: ['name', 'city'] }]
527
+ });
528
+ const visibleRows = filter.apply();
529
+ ```
530
+
531
+ Use `setData`, `setState`, `getState`, `apply`, `clear`, and `addFilter`.
532
+
533
+ ### `gx.zeyos.Client` → `zx.Http`
534
+
535
+ ```js
536
+ // gx
537
+ const client = new gx.zeyos.Client({ url: '/api/' });
538
+ client.get('contacts', { active: true });
539
+
540
+ // Zx
541
+ const client = new zx.Http({ base: '/api/' });
542
+ await client.get('contacts', { active: true });
543
+ ```
544
+
545
+ ### `gx.zeyos.Request` → `zx.zeyosService`
546
+
547
+ ```js
548
+ // gx
549
+ const request = new gx.zeyos.Request({ service: 'contacts', accesskey: token });
550
+ request.post('create', data);
551
+
552
+ // Zx
553
+ const request = zx.zeyosService('contacts', token);
554
+ await request.post('create', data);
555
+ ```
556
+
557
+ `Http` uses promises and exposes `request`, `get`, `post`, `put`, and `delete`. Uploads use
558
+ `request(path, {method, files, data})`. Request failures reject; use `try`/`catch` or `onError`.
559
+
560
+ ### `gx.zeyos.Factory` → `zx.button`, `zx.buttonGroup`, and `zx.icon`
561
+
562
+ ```js
563
+ // gx
564
+ const save = gx.zeyos.Factory.Button('Save', 'primary', 'check');
565
+ const glyph = gx.zeyos.Factory.Icon('trash');
566
+
567
+ // Zx
568
+ const save = zx.button({ label: 'Save', kind: 'primary', icon: 'check' });
569
+ const glyph = zx.icon('trash');
570
+ ```
571
+
572
+ ## Intentionally not reproduced
573
+
574
+ - `gx.core.Settings` and `gx.ui.Container`: direct construction now throws. Use plain option
575
+ objects and extend `zx.Component`; emulating the MooTools inheritance model would retain the
576
+ coupling Zx removes.
577
+ - `gx.ui.Collapse` and `gx.ui.Blend`: compatibility names are migration-error stubs. Use CSS
578
+ transitions guarded by `prefers-reduced-motion`; Zx does not reproduce `Fx.*` timing or the
579
+ MooTools `Class`/`Fx` APIs.
580
+ - `gx.ui.Hud`: use `zx.Dialog` or `zx.Modal` so focus, Escape, and the top layer remain native.
581
+ - `gx.ui.Toggling`: use `zx.Toggle`, `zx.CheckButton`, `zx.Panel`, or direct ARIA/data state.
582
+ - `gx.ui.HGroup`: use application CSS grid/flex layout.
583
+ - `gx.ui.Templates`: use native `<template>`, `zx.h()`, or explicit renderer functions.
584
+ - Legacy bugs are fixed, not emulated: `MenuButton.setStyle`'s typo, the dead
585
+ `MultiValueEditor` delete path, duplicate CheckButton `check`, the Request.File comma bug,
586
+ Tabbox's wrong root, and `Request.delete()` sending PUT. Update tests that depended on those
587
+ defects; `Http.delete()` sends DELETE.
588
+ - `_theme` class-map overrides, including `gx.ui.Table._theme`, are not supported. Override Zx
589
+ semantic tokens or documented component selectors instead.
590
+ - Legacy atomic utility CSS and pixel-identical legacy styling are not shipped. Keep temporary
591
+ app-owned utilities during migration, then replace them with layout CSS and semantic tokens.
592
+ - Fontello numeric glyph codes are not supported. Use the named icons accepted by `zx.icon()`.
593
+ - Full MooTools `getCoordinates()` objects are not emulated. Wrappers expose only
594
+ `width`/`height`/`top`/`left`; migrated code should use `getBoundingClientRect()`.
595
+ - `dispatchEvents` is not reproduced. Use native `dispatchEvent()` or a component's `emit()` and
596
+ listen for bubbling `zx-*` DOM events.
597
+ - `gx.Browser` is not reproduced. Prefer feature detection (`CSS.supports`, element/method
598
+ presence, media queries) over browser-name branching.