@brandup/ui 1.0.44 → 2.0.2

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 (63) hide show
  1. package/README.md +482 -33
  2. package/dist/cjs/constants.js +14 -0
  3. package/dist/cjs/constants.js.map +1 -0
  4. package/dist/cjs/dom/bind-each.js +90 -0
  5. package/dist/cjs/dom/bind-each.js.map +1 -0
  6. package/dist/cjs/dom/bind.js +29 -0
  7. package/dist/cjs/dom/bind.js.map +1 -0
  8. package/dist/cjs/dom/binding-cleanup.js +162 -0
  9. package/dist/cjs/dom/binding-cleanup.js.map +1 -0
  10. package/dist/cjs/dom/dom.js +184 -0
  11. package/dist/cjs/dom/dom.js.map +1 -0
  12. package/dist/cjs/dom/helpers.js +33 -0
  13. package/dist/cjs/dom/helpers.js.map +1 -0
  14. package/dist/cjs/dom/index.js +14 -0
  15. package/dist/cjs/dom/index.js.map +1 -0
  16. package/dist/cjs/dom/tag.js +207 -0
  17. package/dist/cjs/dom/tag.js.map +1 -0
  18. package/dist/cjs/element.js +265 -0
  19. package/dist/cjs/element.js.map +1 -0
  20. package/dist/cjs/events.js +204 -0
  21. package/dist/cjs/events.js.map +1 -0
  22. package/dist/cjs/ext.js +20 -0
  23. package/dist/cjs/ext.js.map +1 -0
  24. package/dist/cjs/index.js +37 -313
  25. package/dist/cjs/index.js.map +1 -1
  26. package/dist/cjs/reactive/computed.js +36 -0
  27. package/dist/cjs/reactive/computed.js.map +1 -0
  28. package/dist/cjs/reactive/effect.js +197 -0
  29. package/dist/cjs/reactive/effect.js.map +1 -0
  30. package/dist/cjs/reactive/reactive.js +106 -0
  31. package/dist/cjs/reactive/reactive.js.map +1 -0
  32. package/dist/mjs/constants.js +10 -0
  33. package/dist/mjs/constants.js.map +1 -0
  34. package/dist/mjs/dom/bind-each.js +86 -0
  35. package/dist/mjs/dom/bind-each.js.map +1 -0
  36. package/dist/mjs/dom/bind.js +26 -0
  37. package/dist/mjs/dom/bind.js.map +1 -0
  38. package/dist/mjs/dom/binding-cleanup.js +156 -0
  39. package/dist/mjs/dom/binding-cleanup.js.map +1 -0
  40. package/dist/mjs/dom/dom.js +169 -0
  41. package/dist/mjs/dom/dom.js.map +1 -0
  42. package/dist/mjs/dom/helpers.js +29 -0
  43. package/dist/mjs/dom/helpers.js.map +1 -0
  44. package/dist/mjs/dom/index.js +12 -0
  45. package/dist/mjs/dom/index.js.map +1 -0
  46. package/dist/mjs/dom/tag.js +203 -0
  47. package/dist/mjs/dom/tag.js.map +1 -0
  48. package/dist/mjs/element.js +260 -0
  49. package/dist/mjs/element.js.map +1 -0
  50. package/dist/mjs/events.js +202 -0
  51. package/dist/mjs/events.js.map +1 -0
  52. package/dist/mjs/ext.js +18 -0
  53. package/dist/mjs/ext.js.map +1 -0
  54. package/dist/mjs/index.js +11 -314
  55. package/dist/mjs/index.js.map +1 -1
  56. package/dist/mjs/reactive/computed.js +33 -0
  57. package/dist/mjs/reactive/computed.js.map +1 -0
  58. package/dist/mjs/reactive/effect.js +187 -0
  59. package/dist/mjs/reactive/effect.js.map +1 -0
  60. package/dist/mjs/reactive/reactive.js +102 -0
  61. package/dist/mjs/reactive/reactive.js.map +1 -0
  62. package/dist/types.d.ts +489 -14
  63. package/package.json +9 -1
@@ -0,0 +1,203 @@
1
+ import { UIElement } from '../element.js';
2
+ import { Binding } from './bind.js';
3
+ import { BindingEach, appendBindingEach } from './bind-each.js';
4
+ import { effect } from '../reactive/effect.js';
5
+ import { autoDisposeBinding } from './binding-cleanup.js';
6
+ import helpers from './helpers.js';
7
+
8
+ /** Returns `true` when `value` should be treated as options (or absent options), `false` when it is the first child. */
9
+ const isOptionsArg = (value) => {
10
+ if (value === null || value === undefined)
11
+ return true;
12
+ if (typeof value !== "object")
13
+ return false; // string, number, boolean, function → child
14
+ if (Array.isArray(value))
15
+ return false; // array → children
16
+ if (value instanceof Element || value instanceof UIElement || value instanceof Binding || value instanceof BindingEach || value instanceof Promise)
17
+ return false;
18
+ return true; // plain object → ElementOptions
19
+ };
20
+ /**
21
+ * Creates an HTML element, optionally applying options and appending children.
22
+ *
23
+ * The second argument is **options** when it is `null` or a plain {@link ElementOptions} object.
24
+ * It is treated as the **first child** for any {@link TagFirstChild} value — strings, numbers,
25
+ * elements, bindings, arrays, etc. — so `tag("div", "hello")` appends "hello" as HTML text,
26
+ * and `tag("ul", bindEach(...))` works without a leading `null`.
27
+ * To apply a CSS class use `{ class: "name" }` in options.
28
+ */
29
+ function tag(tagName, optionsOrChild, ...rest) {
30
+ const elem = document.createElement(tagName);
31
+ if (isOptionsArg(optionsOrChild)) {
32
+ applyOptions(elem, optionsOrChild);
33
+ appendChild(elem, rest);
34
+ }
35
+ else {
36
+ appendChild(elem, optionsOrChild !== undefined ? [optionsOrChild, ...rest] : rest);
37
+ }
38
+ return elem;
39
+ }
40
+ /**
41
+ * Applies element options to an existing element. A string or array is treated as a {@link CssClass}; otherwise each {@link ElementOptions} key is applied (`id`, `styles`, `class`, `command`, `dataset`, `events`, or a plain attribute). `undefined` values are skipped.
42
+ * @param elem Target element to mutate.
43
+ * @param options Options object, {@link CssClass} shorthand, or `null`/`undefined` for none.
44
+ */
45
+ const applyOptions = (elem, options) => {
46
+ if (!options)
47
+ return;
48
+ if (typeof options === "string" || Array.isArray(options))
49
+ helpers.addCssClass(elem, options);
50
+ else {
51
+ for (const key in options) {
52
+ const value = options[key];
53
+ if (value === undefined)
54
+ continue;
55
+ switch (key) {
56
+ case "id":
57
+ elem.id = value;
58
+ break;
59
+ case "styles": {
60
+ if (value) {
61
+ for (const sKey in value)
62
+ elem.style[sKey] = value[sKey];
63
+ }
64
+ break;
65
+ }
66
+ case "class": {
67
+ helpers.addCssClass(elem, value);
68
+ break;
69
+ }
70
+ case "command": {
71
+ elem.dataset["command"] = value;
72
+ break;
73
+ }
74
+ case "dataset": {
75
+ if (value) {
76
+ for (const dataName in value)
77
+ elem.dataset[dataName] = value[dataName];
78
+ }
79
+ break;
80
+ }
81
+ case "events": {
82
+ if (value) {
83
+ for (const eventName in value)
84
+ elem.addEventListener(eventName, value[eventName]);
85
+ }
86
+ break;
87
+ }
88
+ default: {
89
+ if (value === null)
90
+ elem.setAttribute(key, "");
91
+ else if (typeof value === "object")
92
+ elem.setAttribute(key, JSON.stringify(value));
93
+ else
94
+ elem.setAttribute(key, String(value));
95
+ break;
96
+ }
97
+ }
98
+ }
99
+ }
100
+ };
101
+ /**
102
+ * Appends one or more children to a container, recursively resolving arrays, promises and factory functions. Elements are appended as-is; a {@link UIElement} appends its bound element (or defers until `setElement` binds one); a reactive {@link Binding} renders and live-updates in place; strings/numbers/booleans are inserted as HTML; `null`/`undefined` are ignored.
103
+ * @param container Element to append the children to.
104
+ * @param children Child or children to append. See {@link TagChildrenLike}.
105
+ * @throws Error When a child resolves to an unsupported type.
106
+ */
107
+ const appendChild = (container, children) => {
108
+ if (children === null || children === undefined)
109
+ return;
110
+ if (children instanceof Array)
111
+ children.forEach(child => appendChild(container, child));
112
+ else if (children instanceof Element)
113
+ container.append(children);
114
+ else if (children instanceof UIElement) {
115
+ if (children.element)
116
+ container.append(children.element);
117
+ else {
118
+ // reserve the position now; replace the placeholder once setElement
119
+ // binds the element (after _onRenderElement), keeping child order
120
+ const placeholder = document.createComment("");
121
+ container.append(placeholder);
122
+ children.once("rendered", () => {
123
+ if (children.element)
124
+ placeholder.replaceWith(children.element);
125
+ });
126
+ }
127
+ }
128
+ else if (children instanceof Binding)
129
+ appendBinding(container, children);
130
+ else if (children instanceof BindingEach)
131
+ appendBindingEach(container, children);
132
+ else if (children instanceof Promise)
133
+ children.then((child) => appendChild(container, child));
134
+ else {
135
+ const typeName = typeof children;
136
+ let html;
137
+ switch (typeName) {
138
+ case "string":
139
+ html = children;
140
+ break;
141
+ case "number":
142
+ case "boolean":
143
+ html = children.toString();
144
+ break;
145
+ case "function":
146
+ const child = children(container);
147
+ appendChild(container, child);
148
+ return;
149
+ default:
150
+ throw new Error(`Not support child type of ${typeName}.`);
151
+ }
152
+ container.insertAdjacentHTML("beforeend", html);
153
+ }
154
+ };
155
+ /**
156
+ * Renders a reactive {@link Binding} child and keeps it up to date: a reactive
157
+ * effect re-evaluates the binding and updates the DOM in place — reusing a text
158
+ * node for text values and swapping the node when an element/UIElement is returned.
159
+ */
160
+ const appendBinding = (container, binding) => {
161
+ let current = document.createTextNode("");
162
+ let textNode = current;
163
+ container.append(current);
164
+ const eff = effect(() => {
165
+ const value = binding.compute();
166
+ if (value instanceof Element || value instanceof UIElement) {
167
+ const next = (value instanceof UIElement ? value.element : value) ?? document.createComment("");
168
+ current.replaceWith(next);
169
+ current = next;
170
+ textNode = null;
171
+ // deferred UIElement: its element is bound later — swap the placeholder
172
+ // for the real element once setElement raises "rendered"
173
+ if (value instanceof UIElement && !value.element) {
174
+ const placeholder = next;
175
+ value.once("rendered", () => {
176
+ // skip if the binding has since re-rendered to a different node
177
+ if (value.element && current === placeholder) {
178
+ placeholder.replaceWith(value.element);
179
+ current = value.element;
180
+ }
181
+ });
182
+ }
183
+ }
184
+ else {
185
+ // null/undefined/false render as empty text
186
+ const text = (value === null || value === undefined || value === false) ? "" : String(value);
187
+ if (textNode && textNode === current) {
188
+ textNode.textContent = text;
189
+ }
190
+ else {
191
+ const next = document.createTextNode(text);
192
+ current.replaceWith(next);
193
+ current = next;
194
+ textNode = next;
195
+ }
196
+ }
197
+ });
198
+ // stop the effect when the rendered node leaves the document
199
+ autoDisposeBinding(container, () => current, eff);
200
+ };
201
+
202
+ export { appendChild, applyOptions, tag };
203
+ //# sourceMappingURL=tag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag.js","sources":["../../../../source/dom/tag.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -0,0 +1,260 @@
1
+ import { EventEmitter } from './events.js';
2
+ import { effectScope } from './reactive/effect.js';
3
+ import { trackAutoDestroy, destroyUIElementsWithin, untrackAutoDestroy, disposeBindingsWithin } from './dom/binding-cleanup.js';
4
+ import constants from './constants.js';
5
+
6
+ /**
7
+ * Wraps an `HTMLElement` and binds business logic, commands and events to it.
8
+ *
9
+ * The optional `TEvents` event map is merged with {@link UIElementEvents}, so subclasses
10
+ * can declare their own typed events in addition to the built-in `command`/`destroy`.
11
+ */
12
+ class UIElement extends EventEmitter {
13
+ __element;
14
+ __events;
15
+ __commands;
16
+ __destroyed;
17
+ // Element members
18
+ /** The bound DOM element, or `undefined` until `setElement` is called. */
19
+ get element() { return this.__element; }
20
+ /**
21
+ * Bind a DOM element to this instance and run render logic. Can be called only once.
22
+ * @param elem Element to bind; throws if already bound or owned by another `UIElement`.
23
+ */
24
+ setElement(elem) {
25
+ if (!elem)
26
+ throw new Error("Not set value elem.");
27
+ if (this.__element || UIElement.hasElement(elem))
28
+ throw new Error("UIElement already defined");
29
+ this.__element = elem;
30
+ elem[constants.ElemPropertyName] = this;
31
+ elem.dataset[constants.ElemAttributeName] = this.typeName;
32
+ trackAutoDestroy(elem, () => this.destroy());
33
+ this._onRenderElement(elem);
34
+ this.__raise("rendered", this);
35
+ }
36
+ // static members
37
+ /**
38
+ * Whether the given element is already bound to a `UIElement`.
39
+ * @param elem Element to test.
40
+ */
41
+ static hasElement(elem) {
42
+ return !!elem.dataset[constants.ElemAttributeName];
43
+ }
44
+ // Command members
45
+ /**
46
+ * Register a handler for a command declared in markup via the `data-command` attribute.
47
+ * @param name Command name (case-insensitive); throws if already registered.
48
+ * @param execute Handler run when the command fires; may return a `Promise` for async commands.
49
+ * @param canExecute Optional predicate gating whether the command may run.
50
+ * @returns This instance for chaining.
51
+ */
52
+ registerCommand(name, execute, canExecute) {
53
+ if (this.__destroyed)
54
+ return this;
55
+ const commands = this.__commands || (this.__commands = {});
56
+ const normalizedName = name.toLowerCase();
57
+ if (normalizedName in commands)
58
+ throw new Error(`Command "${name}" already registered.`);
59
+ commands[normalizedName] = {
60
+ name: name,
61
+ execute,
62
+ canExecute
63
+ };
64
+ return this;
65
+ }
66
+ /**
67
+ * Whether a command with the given name is registered.
68
+ * @param name Command name (case-insensitive).
69
+ */
70
+ hasCommand(name) {
71
+ return !!this.__commands && name.toLowerCase() in this.__commands;
72
+ }
73
+ /**
74
+ * Execute a registered command against a target element.
75
+ * @internal
76
+ */
77
+ __execCommand(name, target) {
78
+ if (this.__destroyed || !this.__element)
79
+ throw new Error("UIElement is destroyed or has no element.");
80
+ const key = name.toLowerCase();
81
+ const command = this.__commands?.[key];
82
+ if (!command)
83
+ throw new Error(`Command "${name}" is not registered.`);
84
+ const context = {
85
+ target,
86
+ uiElem: this
87
+ };
88
+ if (command.isExecuting)
89
+ return { status: "already", context };
90
+ command.isExecuting = true;
91
+ // keep isExecuting cleanup inside finally so a throw in the
92
+ // guards/trigger/execute can never leave the command stuck.
93
+ let isAsync = false;
94
+ try {
95
+ if (!this._onCanExecCommand(name, target))
96
+ return { status: "disallow", context };
97
+ if (command.canExecute && !command.canExecute(context))
98
+ return { status: "disallow", context };
99
+ this.__raise("command", { element: this, name: command.name });
100
+ const commandResult = command.execute(context);
101
+ if (commandResult instanceof Promise) {
102
+ isAsync = true;
103
+ target.classList.add(constants.CommandExecutingCssClassName);
104
+ commandResult
105
+ // command owns its errors; log so failures aren't silent, and avoid unhandled rejection
106
+ .catch((reason) => console.error(`Command "${command.name}" failed.`, reason))
107
+ .finally(() => {
108
+ target.classList.remove(constants.CommandExecutingCssClassName);
109
+ delete command.isExecuting;
110
+ });
111
+ }
112
+ return { status: "success", context };
113
+ }
114
+ finally {
115
+ if (!isAsync)
116
+ delete command.isExecuting;
117
+ }
118
+ }
119
+ /**
120
+ * Hook invoked when an element is bound via `setElement`. Override to render or wire up the element.
121
+ * @param _elem The newly bound element.
122
+ */
123
+ /** Trigger a built-in event. Typed by {@link UIElementEvents}; bypasses the generic trigger overload internally. */
124
+ __raise(name, ...args) {
125
+ this.trigger(name, ...args);
126
+ }
127
+ _onRenderElement(_elem) { }
128
+ /**
129
+ * Hook deciding whether a command may execute. Override to add element-wide gating.
130
+ * @param _name Command name being executed.
131
+ * @param _elem Target element of the command.
132
+ * @returns `true` to allow execution (default), `false` to disallow.
133
+ */
134
+ _onCanExecCommand(_name, _elem) {
135
+ return true;
136
+ }
137
+ /**
138
+ * Create an {@link EffectScope} whose reactive effects are stopped automatically when
139
+ * this element is destroyed. Use it to scope `bind`/`effect` to the element's lifetime.
140
+ */
141
+ effectScope() {
142
+ const scope = effectScope();
143
+ this.on("destroy", () => scope.stop());
144
+ return scope;
145
+ }
146
+ /** Returns the `typeName` of this element. */
147
+ toString() { return this.typeName; }
148
+ /** Destroy the element: trigger the `destroy` event, release events/commands and detach from the DOM element. */
149
+ destroy() {
150
+ if (this.__destroyed)
151
+ return;
152
+ this.__destroyed = true;
153
+ this.__raise("destroy", this);
154
+ super.stopEvents();
155
+ const elem = this.__element;
156
+ if (elem) {
157
+ destroyUIElementsWithin(elem); // cascade to nested UIElements, deepest first
158
+ untrackAutoDestroy(elem);
159
+ disposeBindingsWithin(elem);
160
+ delete elem.dataset[constants.ElemAttributeName];
161
+ delete elem[constants.ElemPropertyName];
162
+ }
163
+ delete this.__element;
164
+ delete this.__events;
165
+ delete this.__commands;
166
+ }
167
+ }
168
+ /**
169
+ * A {@link UIElement} whose DOM element is bound in the constructor, so its `element`
170
+ * is always defined (typed `HTMLElement`, never `undefined`). Subclasses pass their
171
+ * `typeName` and the element to `super`.
172
+ *
173
+ * Use {@link UIElement} directly when the element is bound later (e.g. an application
174
+ * that binds its element on run).
175
+ */
176
+ class UIElementBound extends UIElement {
177
+ __typeName;
178
+ /** @param typeName Unique type name of this element. @param elem Element to bind. */
179
+ constructor(typeName, elem) {
180
+ super();
181
+ this.__typeName = typeName; // set before setElement (no field-initializer race)
182
+ this.setElement(elem);
183
+ }
184
+ get typeName() { return this.__typeName; }
185
+ /** The bound DOM element; always defined since it is set in the constructor. */
186
+ get element() { return super.element; }
187
+ }
188
+ const findUiElementByCommand = (elem, commandName) => {
189
+ let current = elem;
190
+ while (current) {
191
+ if (current.dataset[constants.ElemAttributeName]) {
192
+ const uiElem = current[constants.ElemPropertyName];
193
+ if (uiElem.hasCommand(commandName))
194
+ return uiElem;
195
+ }
196
+ current = current.parentElement;
197
+ }
198
+ return null;
199
+ };
200
+ const commandClickHandler = (e) => {
201
+ // walk up from the clicked element to the nearest ancestor declaring a command
202
+ let commandElem = e.target;
203
+ while (commandElem && !commandElem.dataset[constants.CommandAttributeName])
204
+ commandElem = commandElem.parentElement;
205
+ if (!commandElem)
206
+ return;
207
+ const commandName = commandElem.dataset[constants.CommandAttributeName];
208
+ if (!commandName)
209
+ throw new Error("Command data attribute does not have a value.");
210
+ const uiElem = findUiElementByCommand(commandElem, commandName);
211
+ // A click on a data-command element is owned by the library: prevent the element's
212
+ // default action (e.g. <a> navigation) and stop the click chain — UNLESS the command
213
+ // completed successfully and asked to stay transparent. This runs in `finally`, so a
214
+ // throwing command can never skip preventDefault and let the page navigate/reload.
215
+ let transparent = false;
216
+ try {
217
+ if (uiElem) {
218
+ const result = uiElem.__execCommand(commandName, commandElem);
219
+ transparent = result.status === "success" && !!result.context.transparent;
220
+ }
221
+ else
222
+ console.warn(`Not find handler for command "${commandName}".`);
223
+ }
224
+ catch (reason) {
225
+ console.error(`Command "${commandName}" failed.`, reason);
226
+ }
227
+ finally {
228
+ if (!transparent) {
229
+ e.preventDefault();
230
+ e.stopPropagation();
231
+ e.stopImmediatePropagation();
232
+ }
233
+ }
234
+ };
235
+ let __commandsInited = false;
236
+ /**
237
+ * Register the global click handler that dispatches {@link UIElement} commands declared in
238
+ * markup via `data-command`. Call once during application startup. Idempotent and a no-op in
239
+ * a non-DOM environment. {@link Application.run} (in `@brandup/ui-app`) calls this automatically;
240
+ * call it yourself only when using `UIElement` commands without an `Application`.
241
+ *
242
+ * Making this explicit (instead of a side effect on import) lets bundlers tree-shake the
243
+ * command system away for consumers that don't use it.
244
+ */
245
+ function initUICommands() {
246
+ if (__commandsInited || typeof window === "undefined")
247
+ return;
248
+ __commandsInited = true;
249
+ window.addEventListener("click", commandClickHandler);
250
+ }
251
+ /** Remove the global command click handler registered by {@link initUICommands} (e.g. on HMR disposal or teardown). */
252
+ function destroyUI() {
253
+ if (!__commandsInited || typeof window === "undefined")
254
+ return;
255
+ __commandsInited = false;
256
+ window.removeEventListener("click", commandClickHandler);
257
+ }
258
+
259
+ export { UIElement, UIElementBound, destroyUI, initUICommands };
260
+ //# sourceMappingURL=element.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element.js","sources":["../../../source/element.ts"],"sourcesContent":[null],"names":["UICONSTANTS","createEffectScope"],"mappings":";;;;;AAuBA;;;;;AAKG;AACG,MAAgB,SAAwB,SAAQ,YAAmC,CAAA;AAChF,IAAA,SAAS;AACT,IAAA,QAAQ;AACR,IAAA,UAAU;AACV,IAAA,WAAW;;;IAQnB,IAAI,OAAO,KAA8B,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC;AAEhE;;;AAGG;AACO,IAAA,UAAU,CAAC,IAAiB,EAAA;AACrC,QAAA,IAAI,CAAC,IAAI;AACR,YAAA,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC;QAEvC,IAAI,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC;AAC/C,YAAA,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC;AAE7C,QAAA,IAAI,CAAC,SAAS,GAAG,IAAI;AAEf,QAAA,IAAK,CAACA,SAAW,CAAC,gBAAgB,CAAC,GAAG,IAAI;QAChD,IAAI,CAAC,OAAO,CAACA,SAAW,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC,QAAQ;QAE3D,gBAAgB,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;AAE5C,QAAA,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC;AAE3B,QAAA,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC;IAC/B;;AAIA;;;AAGG;IACH,OAAO,UAAU,CAAC,IAAiB,EAAA;QAClC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAACA,SAAW,CAAC,iBAAiB,CAAC;IACrD;;AAIA;;;;;;AAMG;AACH,IAAA,eAAe,CAAC,IAAY,EAAE,OAA+B,EAAE,UAAsC,EAAA;QACpG,IAAI,IAAI,CAAC,WAAW;AACnB,YAAA,OAAO,IAAI;AAEZ,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,KAAK,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;AAE1D,QAAA,MAAM,cAAc,GAAG,IAAI,CAAC,WAAW,EAAE;QACzC,IAAI,cAAc,IAAI,QAAQ;AAC7B,YAAA,MAAM,IAAI,KAAK,CAAC,YAAY,IAAI,CAAA,qBAAA,CAAuB,CAAC;QAEzD,QAAQ,CAAC,cAAc,CAAC,GAAG;AAC1B,YAAA,IAAI,EAAE,IAAI;YACV,OAAO;YACP;SACA;AAED,QAAA,OAAO,IAAI;IACZ;AAEA;;;AAGG;AACH,IAAA,UAAU,CAAC,IAAY,EAAA;AACtB,QAAA,OAAO,CAAC,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,WAAW,EAAE,IAAI,IAAI,CAAC,UAAU;IAClE;AAEA;;;AAGG;IACH,aAAa,CAAC,IAAY,EAAE,MAAmB,EAAA;AAC9C,QAAA,IAAI,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,SAAS;AACtC,YAAA,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC;AAE7D,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC;AACtC,QAAA,IAAI,CAAC,OAAO;AACX,YAAA,MAAM,IAAI,KAAK,CAAC,YAAY,IAAI,CAAA,oBAAA,CAAsB,CAAC;AAExD,QAAA,MAAM,OAAO,GAAmB;YAC/B,MAAM;AACN,YAAA,MAAM,EAAE;SACR;QAED,IAAI,OAAO,CAAC,WAAW;AACtB,YAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE;AACtC,QAAA,OAAO,CAAC,WAAW,GAAG,IAAI;;;QAI1B,IAAI,OAAO,GAAG,KAAK;AACnB,QAAA,IAAI;YACH,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC;AACxC,gBAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE;YAEvC,IAAI,OAAO,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC;AACrD,gBAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE;AAEvC,YAAA,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;YAE9D,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;AAE9C,YAAA,IAAI,aAAa,YAAY,OAAO,EAAE;gBACrC,OAAO,GAAG,IAAI;gBAEd,MAAM,CAAC,SAAS,CAAC,GAAG,CAACA,SAAW,CAAC,4BAA4B,CAAC;gBAC9D;;AAEE,qBAAA,KAAK,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC,CAAA,SAAA,EAAY,OAAO,CAAC,IAAI,WAAW,EAAE,MAAM,CAAC;qBAC5E,OAAO,CAAC,MAAK;oBACb,MAAM,CAAC,SAAS,CAAC,MAAM,CAACA,SAAW,CAAC,4BAA4B,CAAC;oBACjE,OAAO,OAAO,CAAC,WAAW;AAC3B,gBAAA,CAAC,CAAC;YACJ;AAEA,YAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE;QACtC;gBACQ;AACP,YAAA,IAAI,CAAC,OAAO;gBACX,OAAO,OAAO,CAAC,WAAW;QAC5B;IACD;AAEA;;;AAGG;;AAEK,IAAA,OAAO,CAAkC,IAAO,EAAE,GAAG,IAAoC,EAAA;QAC/F,IAAI,CAAC,OAAuD,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC;IAC7E;IAEU,gBAAgB,CAAC,KAAkB,EAAA,EAAI;AAEjD;;;;;AAKG;IACO,iBAAiB,CAAC,KAAa,EAAE,KAAkB,EAAA;AAC5D,QAAA,OAAO,IAAI;IACZ;AAEA;;;AAGG;IACH,WAAW,GAAA;AACV,QAAA,MAAM,KAAK,GAAGC,WAAiB,EAAE;AACjC,QAAA,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;AACtC,QAAA,OAAO,KAAK;IACb;;AAGS,IAAA,QAAQ,KAAa,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC;;IAGpD,OAAO,GAAA;QACN,IAAI,IAAI,CAAC,WAAW;YACnB;AACD,QAAA,IAAI,CAAC,WAAW,GAAG,IAAI;AAEvB,QAAA,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC;QAC7B,KAAK,CAAC,UAAU,EAAE;AAElB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS;QAC3B,IAAI,IAAI,EAAE;AACT,YAAA,uBAAuB,CAAC,IAAI,CAAC,CAAC;YAC9B,kBAAkB,CAAC,IAAI,CAAC;YACxB,qBAAqB,CAAC,IAAI,CAAC;YAC3B,OAAO,IAAI,CAAC,OAAO,CAACD,SAAW,CAAC,iBAAiB,CAAC;AAClD,YAAA,OAAa,IAAK,CAACA,SAAW,CAAC,gBAAgB,CAAC;QACjD;QAEA,OAAO,IAAI,CAAC,SAAS;QACrB,OAAO,IAAI,CAAC,QAAQ;QACpB,OAAO,IAAI,CAAC,UAAU;IACvB;AACA;AAED;;;;;;;AAOG;AACG,MAAgB,cAA6B,SAAQ,SAAkB,CAAA;AACpE,IAAA,UAAU;;IAGlB,WAAA,CAAY,QAAgB,EAAE,IAAiB,EAAA;AAC9C,QAAA,KAAK,EAAE;AACP,QAAA,IAAI,CAAC,UAAU,GAAG,QAAQ,CAAC;AAC3B,QAAA,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;IACtB;IAEA,IAAI,QAAQ,KAAa,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC;;IAGjD,IAAa,OAAO,KAAkB,OAAO,KAAK,CAAC,OAAsB,CAAC,CAAC;AAC3E;AAED,MAAM,sBAAsB,GAAG,CAAC,IAAiB,EAAE,WAAmB,KAAsB;IAC3F,IAAI,OAAO,GAAuB,IAAI;IACtC,OAAO,OAAO,EAAE;QACf,IAAI,OAAO,CAAC,OAAO,CAACA,SAAW,CAAC,iBAAiB,CAAC,EAAE;YACnD,MAAM,MAAM,GAAoB,OAAQ,CAACA,SAAW,CAAC,gBAAgB,CAAC;AACtE,YAAA,IAAI,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC;AACjC,gBAAA,OAAO,MAAM;QACf;AAEA,QAAA,OAAO,GAAG,OAAO,CAAC,aAAa;IAChC;AAEA,IAAA,OAAO,IAAI;AACZ,CAAC;AAED,MAAM,mBAAmB,GAAG,CAAC,CAAa,KAAI;;AAE7C,IAAA,IAAI,WAAW,GAAuB,CAAC,CAAC,MAA4B;IACpE,OAAO,WAAW,IAAI,CAAC,WAAW,CAAC,OAAO,CAACA,SAAW,CAAC,oBAAoB,CAAC;AAC3E,QAAA,WAAW,GAAG,WAAW,CAAC,aAAa;AAExC,IAAA,IAAI,CAAC,WAAW;QACf;IAED,MAAM,WAAW,GAAG,WAAW,CAAC,OAAO,CAACA,SAAW,CAAC,oBAAoB,CAAC;AACzE,IAAA,IAAI,CAAC,WAAW;AACf,QAAA,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC;IAEjE,MAAM,MAAM,GAAG,sBAAsB,CAAC,WAAW,EAAE,WAAW,CAAC;;;;;IAM/D,IAAI,WAAW,GAAG,KAAK;AACvB,IAAA,IAAI;QACH,IAAI,MAAM,EAAE;YACX,MAAM,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC;AAC7D,YAAA,WAAW,GAAG,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW;QAC1E;;AAEC,YAAA,OAAO,CAAC,IAAI,CAAC,iCAAiC,WAAW,CAAA,EAAA,CAAI,CAAC;IAChE;IACA,OAAO,MAAM,EAAE;QACd,OAAO,CAAC,KAAK,CAAC,CAAA,SAAA,EAAY,WAAW,CAAA,SAAA,CAAW,EAAE,MAAM,CAAC;IAC1D;YACQ;QACP,IAAI,CAAC,WAAW,EAAE;YACjB,CAAC,CAAC,cAAc,EAAE;YAClB,CAAC,CAAC,eAAe,EAAE;YACnB,CAAC,CAAC,wBAAwB,EAAE;QAC7B;IACD;AACD,CAAC;AAED,IAAI,gBAAgB,GAAG,KAAK;AAE5B;;;;;;;;AAQG;SACa,cAAc,GAAA;AAC7B,IAAA,IAAI,gBAAgB,IAAI,OAAO,MAAM,KAAK,WAAW;QACpD;IACD,gBAAgB,GAAG,IAAI;AACvB,IAAA,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,CAAC;AACtD;AAEA;SACgB,SAAS,GAAA;AACxB,IAAA,IAAI,CAAC,gBAAgB,IAAI,OAAO,MAAM,KAAK,WAAW;QACrD;IACD,gBAAgB,GAAG,KAAK;AACxB,IAAA,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,CAAC;AACzD;;;;"}