@brandup/ui 2.0.1 → 2.0.3

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 +12 -7
  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 +40 -1455
  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 -1431
  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 +18 -2
  63. package/package.json +9 -1
@@ -0,0 +1,207 @@
1
+ 'use strict';
2
+
3
+ var element = require('../element.js');
4
+ var bind = require('./bind.js');
5
+ var bindEach = require('./bind-each.js');
6
+ var effect = require('../reactive/effect.js');
7
+ var bindingCleanup = require('./binding-cleanup.js');
8
+ var helpers = require('./helpers.js');
9
+
10
+ /** Returns `true` when `value` should be treated as options (or absent options), `false` when it is the first child. */
11
+ const isOptionsArg = (value) => {
12
+ if (value === null || value === undefined)
13
+ return true;
14
+ if (typeof value !== "object")
15
+ return false; // string, number, boolean, function → child
16
+ if (Array.isArray(value))
17
+ return false; // array → children
18
+ if (value instanceof Element || value instanceof element.UIElement || value instanceof bind.Binding || value instanceof bindEach.BindingEach || value instanceof Promise)
19
+ return false;
20
+ return true; // plain object → ElementOptions
21
+ };
22
+ /**
23
+ * Creates an HTML element, optionally applying options and appending children.
24
+ *
25
+ * The second argument is **options** when it is `null` or a plain {@link ElementOptions} object.
26
+ * It is treated as the **first child** for any {@link TagFirstChild} value — strings, numbers,
27
+ * elements, bindings, arrays, etc. — so `tag("div", "hello")` appends "hello" as HTML text,
28
+ * and `tag("ul", bindEach(...))` works without a leading `null`.
29
+ * To apply a CSS class use `{ class: "name" }` in options.
30
+ */
31
+ function tag(tagName, optionsOrChild, ...rest) {
32
+ const elem = document.createElement(tagName);
33
+ if (isOptionsArg(optionsOrChild)) {
34
+ applyOptions(elem, optionsOrChild);
35
+ appendChild(elem, rest);
36
+ }
37
+ else {
38
+ appendChild(elem, optionsOrChild !== undefined ? [optionsOrChild, ...rest] : rest);
39
+ }
40
+ return elem;
41
+ }
42
+ /**
43
+ * 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.
44
+ * @param elem Target element to mutate.
45
+ * @param options Options object, {@link CssClass} shorthand, or `null`/`undefined` for none.
46
+ */
47
+ const applyOptions = (elem, options) => {
48
+ if (!options)
49
+ return;
50
+ if (typeof options === "string" || Array.isArray(options))
51
+ helpers.default.addCssClass(elem, options);
52
+ else {
53
+ for (const key in options) {
54
+ const value = options[key];
55
+ if (value === undefined)
56
+ continue;
57
+ switch (key) {
58
+ case "id":
59
+ elem.id = value;
60
+ break;
61
+ case "styles": {
62
+ if (value) {
63
+ for (const sKey in value)
64
+ elem.style[sKey] = value[sKey];
65
+ }
66
+ break;
67
+ }
68
+ case "class": {
69
+ helpers.default.addCssClass(elem, value);
70
+ break;
71
+ }
72
+ case "command": {
73
+ elem.dataset["command"] = value;
74
+ break;
75
+ }
76
+ case "dataset": {
77
+ if (value) {
78
+ for (const dataName in value)
79
+ elem.dataset[dataName] = value[dataName];
80
+ }
81
+ break;
82
+ }
83
+ case "events": {
84
+ if (value) {
85
+ for (const eventName in value)
86
+ elem.addEventListener(eventName, value[eventName]);
87
+ }
88
+ break;
89
+ }
90
+ default: {
91
+ if (value === null)
92
+ elem.setAttribute(key, "");
93
+ else if (typeof value === "object")
94
+ elem.setAttribute(key, JSON.stringify(value));
95
+ else
96
+ elem.setAttribute(key, String(value));
97
+ break;
98
+ }
99
+ }
100
+ }
101
+ }
102
+ };
103
+ /**
104
+ * 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.
105
+ * @param container Element to append the children to.
106
+ * @param children Child or children to append. See {@link TagChildrenLike}.
107
+ * @throws Error When a child resolves to an unsupported type.
108
+ */
109
+ const appendChild = (container, children) => {
110
+ if (children === null || children === undefined)
111
+ return;
112
+ if (children instanceof Array)
113
+ children.forEach(child => appendChild(container, child));
114
+ else if (children instanceof Element)
115
+ container.append(children);
116
+ else if (children instanceof element.UIElement) {
117
+ if (children.element)
118
+ container.append(children.element);
119
+ else {
120
+ // reserve the position now; replace the placeholder once setElement
121
+ // binds the element (after _onRenderElement), keeping child order
122
+ const placeholder = document.createComment("");
123
+ container.append(placeholder);
124
+ children.once("rendered", () => {
125
+ if (children.element)
126
+ placeholder.replaceWith(children.element);
127
+ });
128
+ }
129
+ }
130
+ else if (children instanceof bind.Binding)
131
+ appendBinding(container, children);
132
+ else if (children instanceof bindEach.BindingEach)
133
+ bindEach.appendBindingEach(container, children);
134
+ else if (children instanceof Promise)
135
+ children.then((child) => appendChild(container, child));
136
+ else {
137
+ const typeName = typeof children;
138
+ let html;
139
+ switch (typeName) {
140
+ case "string":
141
+ html = children;
142
+ break;
143
+ case "number":
144
+ case "boolean":
145
+ html = children.toString();
146
+ break;
147
+ case "function":
148
+ const child = children(container);
149
+ appendChild(container, child);
150
+ return;
151
+ default:
152
+ throw new Error(`Not support child type of ${typeName}.`);
153
+ }
154
+ container.insertAdjacentHTML("beforeend", html);
155
+ }
156
+ };
157
+ /**
158
+ * Renders a reactive {@link Binding} child and keeps it up to date: a reactive
159
+ * effect re-evaluates the binding and updates the DOM in place — reusing a text
160
+ * node for text values and swapping the node when an element/UIElement is returned.
161
+ */
162
+ const appendBinding = (container, binding) => {
163
+ let current = document.createTextNode("");
164
+ let textNode = current;
165
+ container.append(current);
166
+ const eff = effect.effect(() => {
167
+ const value = binding.compute();
168
+ if (value instanceof Element || value instanceof element.UIElement) {
169
+ const next = (value instanceof element.UIElement ? value.element : value) ?? document.createComment("");
170
+ current.replaceWith(next);
171
+ current = next;
172
+ textNode = null;
173
+ // deferred UIElement: its element is bound later — swap the placeholder
174
+ // for the real element once setElement raises "rendered"
175
+ if (value instanceof element.UIElement && !value.element) {
176
+ const placeholder = next;
177
+ value.once("rendered", () => {
178
+ // skip if the binding has since re-rendered to a different node
179
+ if (value.element && current === placeholder) {
180
+ placeholder.replaceWith(value.element);
181
+ current = value.element;
182
+ }
183
+ });
184
+ }
185
+ }
186
+ else {
187
+ // null/undefined/false render as empty text
188
+ const text = (value === null || value === undefined || value === false) ? "" : String(value);
189
+ if (textNode && textNode === current) {
190
+ textNode.textContent = text;
191
+ }
192
+ else {
193
+ const next = document.createTextNode(text);
194
+ current.replaceWith(next);
195
+ current = next;
196
+ textNode = next;
197
+ }
198
+ }
199
+ });
200
+ // stop the effect when the rendered node leaves the document
201
+ bindingCleanup.autoDisposeBinding(container, () => current, eff);
202
+ };
203
+
204
+ exports.appendChild = appendChild;
205
+ exports.applyOptions = applyOptions;
206
+ exports.tag = tag;
207
+ //# sourceMappingURL=tag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag.js","sources":["../../../../source/dom/tag.ts"],"sourcesContent":[null],"names":["UIElement","Binding","BindingEach","helpers","appendBindingEach","effect","autoDisposeBinding"],"mappings":"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"}
@@ -0,0 +1,265 @@
1
+ 'use strict';
2
+
3
+ var events = require('./events.js');
4
+ var effect = require('./reactive/effect.js');
5
+ var bindingCleanup = require('./dom/binding-cleanup.js');
6
+ var constants = require('./constants.js');
7
+
8
+ /**
9
+ * Wraps an `HTMLElement` and binds business logic, commands and events to it.
10
+ *
11
+ * The optional `TEvents` event map is merged with {@link UIElementEvents}, so subclasses
12
+ * can declare their own typed events in addition to the built-in `command`/`destroy`.
13
+ */
14
+ class UIElement extends events.EventEmitter {
15
+ __element;
16
+ __events;
17
+ __commands;
18
+ __destroyed;
19
+ // Element members
20
+ /** The bound DOM element, or `undefined` until `setElement` is called. */
21
+ get element() { return this.__element; }
22
+ /**
23
+ * Bind a DOM element to this instance and run render logic. Can be called only once.
24
+ * @param elem Element to bind; throws if already bound or owned by another `UIElement`.
25
+ */
26
+ setElement(elem) {
27
+ if (!elem)
28
+ throw new Error("Not set value elem.");
29
+ if (this.__element || UIElement.hasElement(elem))
30
+ throw new Error("UIElement already defined");
31
+ this.__element = elem;
32
+ elem[constants.default.ElemPropertyName] = this;
33
+ elem.dataset[constants.default.ElemAttributeName] = this.typeName;
34
+ bindingCleanup.trackAutoDestroy(elem, () => this.destroy());
35
+ this._onRenderElement(elem);
36
+ this.__raise("rendered", this);
37
+ }
38
+ // static members
39
+ /**
40
+ * Whether the given element is already bound to a `UIElement`.
41
+ * @param elem Element to test.
42
+ */
43
+ static hasElement(elem) {
44
+ return !!elem.dataset[constants.default.ElemAttributeName];
45
+ }
46
+ // Command members
47
+ /**
48
+ * Register a handler for a command declared in markup via the `data-command` attribute.
49
+ * @param name Command name (case-insensitive); throws if already registered.
50
+ * @param execute Handler run when the command fires; may return a `Promise` for async commands.
51
+ * @param canExecute Optional predicate gating whether the command may run.
52
+ * @returns This instance for chaining.
53
+ */
54
+ registerCommand(name, execute, canExecute) {
55
+ if (this.__destroyed)
56
+ return this;
57
+ const commands = this.__commands || (this.__commands = {});
58
+ const normalizedName = name.toLowerCase();
59
+ if (normalizedName in commands)
60
+ throw new Error(`Command "${name}" already registered.`);
61
+ commands[normalizedName] = {
62
+ name: name,
63
+ execute,
64
+ canExecute
65
+ };
66
+ return this;
67
+ }
68
+ /**
69
+ * Whether a command with the given name is registered.
70
+ * @param name Command name (case-insensitive).
71
+ */
72
+ hasCommand(name) {
73
+ return !!this.__commands && name.toLowerCase() in this.__commands;
74
+ }
75
+ /**
76
+ * Execute a registered command against a target element.
77
+ * @internal
78
+ */
79
+ __execCommand(name, target) {
80
+ if (this.__destroyed || !this.__element)
81
+ throw new Error("UIElement is destroyed or has no element.");
82
+ const key = name.toLowerCase();
83
+ const command = this.__commands?.[key];
84
+ if (!command)
85
+ throw new Error(`Command "${name}" is not registered.`);
86
+ const context = {
87
+ target,
88
+ uiElem: this
89
+ };
90
+ if (command.isExecuting)
91
+ return { status: "already", context };
92
+ command.isExecuting = true;
93
+ // keep isExecuting cleanup inside finally so a throw in the
94
+ // guards/trigger/execute can never leave the command stuck.
95
+ let isAsync = false;
96
+ try {
97
+ if (!this._onCanExecCommand(name, target))
98
+ return { status: "disallow", context };
99
+ if (command.canExecute && !command.canExecute(context))
100
+ return { status: "disallow", context };
101
+ this.__raise("command", { element: this, name: command.name });
102
+ const commandResult = command.execute(context);
103
+ if (commandResult instanceof Promise) {
104
+ isAsync = true;
105
+ target.classList.add(constants.default.CommandExecutingCssClassName);
106
+ commandResult
107
+ // command owns its errors; log so failures aren't silent, and avoid unhandled rejection
108
+ .catch((reason) => console.error(`Command "${command.name}" failed.`, reason))
109
+ .finally(() => {
110
+ target.classList.remove(constants.default.CommandExecutingCssClassName);
111
+ delete command.isExecuting;
112
+ });
113
+ }
114
+ return { status: "success", context };
115
+ }
116
+ finally {
117
+ if (!isAsync)
118
+ delete command.isExecuting;
119
+ }
120
+ }
121
+ /**
122
+ * Hook invoked when an element is bound via `setElement`. Override to render or wire up the element.
123
+ * @param _elem The newly bound element.
124
+ */
125
+ /** Trigger a built-in event. Typed by {@link UIElementEvents}; bypasses the generic trigger overload internally. */
126
+ __raise(name, ...args) {
127
+ this.trigger(name, ...args);
128
+ }
129
+ _onRenderElement(_elem) { }
130
+ /**
131
+ * Hook deciding whether a command may execute. Override to add element-wide gating.
132
+ * @param _name Command name being executed.
133
+ * @param _elem Target element of the command.
134
+ * @returns `true` to allow execution (default), `false` to disallow.
135
+ */
136
+ _onCanExecCommand(_name, _elem) {
137
+ return true;
138
+ }
139
+ /**
140
+ * Create an {@link EffectScope} whose reactive effects are stopped automatically when
141
+ * this element is destroyed. Use it to scope `bind`/`effect` to the element's lifetime.
142
+ */
143
+ effectScope() {
144
+ const scope = effect.effectScope();
145
+ this.on("destroy", () => scope.stop());
146
+ return scope;
147
+ }
148
+ /** Returns the `typeName` of this element. */
149
+ toString() { return this.typeName; }
150
+ /** Destroy the element: trigger the `destroy` event, release events/commands and detach from the DOM element. */
151
+ destroy() {
152
+ if (this.__destroyed)
153
+ return;
154
+ this.__destroyed = true;
155
+ this.__raise("destroy", this);
156
+ super.stopEvents();
157
+ const elem = this.__element;
158
+ if (elem) {
159
+ bindingCleanup.destroyUIElementsWithin(elem); // cascade to nested UIElements, deepest first
160
+ bindingCleanup.untrackAutoDestroy(elem);
161
+ bindingCleanup.disposeBindingsWithin(elem);
162
+ delete elem.dataset[constants.default.ElemAttributeName];
163
+ delete elem[constants.default.ElemPropertyName];
164
+ }
165
+ delete this.__element;
166
+ delete this.__events;
167
+ delete this.__commands;
168
+ }
169
+ }
170
+ /**
171
+ * A {@link UIElement} whose DOM element is bound in the constructor, so its `element`
172
+ * is always defined (typed `HTMLElement`, never `undefined`). Subclasses pass their
173
+ * `typeName` and the element to `super`.
174
+ *
175
+ * Use {@link UIElement} directly when the element is bound later (e.g. an application
176
+ * that binds its element on run).
177
+ */
178
+ class UIElementBound extends UIElement {
179
+ __typeName;
180
+ /** @param typeName Unique type name of this element. @param elem Element to bind. */
181
+ constructor(typeName, elem) {
182
+ super();
183
+ this.__typeName = typeName; // set before setElement (no field-initializer race)
184
+ this.setElement(elem);
185
+ }
186
+ get typeName() { return this.__typeName; }
187
+ /** The bound DOM element; always defined since it is set in the constructor. */
188
+ get element() { return super.element; }
189
+ }
190
+ const findUiElementByCommand = (elem, commandName) => {
191
+ let current = elem;
192
+ while (current) {
193
+ if (current.dataset[constants.default.ElemAttributeName]) {
194
+ const uiElem = current[constants.default.ElemPropertyName];
195
+ if (uiElem.hasCommand(commandName))
196
+ return uiElem;
197
+ }
198
+ current = current.parentElement;
199
+ }
200
+ return null;
201
+ };
202
+ const commandClickHandler = (e) => {
203
+ // walk up from the clicked element to the nearest ancestor declaring a command
204
+ let commandElem = e.target;
205
+ while (commandElem && !commandElem.dataset[constants.default.CommandAttributeName])
206
+ commandElem = commandElem.parentElement;
207
+ if (!commandElem)
208
+ return;
209
+ const commandName = commandElem.dataset[constants.default.CommandAttributeName];
210
+ if (!commandName)
211
+ throw new Error("Command data attribute does not have a value.");
212
+ const uiElem = findUiElementByCommand(commandElem, commandName);
213
+ // A click on a data-command element is owned by the library: prevent the element's
214
+ // default action (e.g. <a> navigation) and stop the click chain — UNLESS the command
215
+ // completed successfully and asked to stay transparent. This runs in `finally`, so a
216
+ // throwing command can never skip preventDefault and let the page navigate/reload.
217
+ let transparent = false;
218
+ try {
219
+ if (uiElem) {
220
+ const result = uiElem.__execCommand(commandName, commandElem);
221
+ transparent = result.status === "success" && !!result.context.transparent;
222
+ }
223
+ else
224
+ console.warn(`Not find handler for command "${commandName}".`);
225
+ }
226
+ catch (reason) {
227
+ console.error(`Command "${commandName}" failed.`, reason);
228
+ }
229
+ finally {
230
+ if (!transparent) {
231
+ e.preventDefault();
232
+ e.stopPropagation();
233
+ e.stopImmediatePropagation();
234
+ }
235
+ }
236
+ };
237
+ let __commandsInited = false;
238
+ /**
239
+ * Register the global click handler that dispatches {@link UIElement} commands declared in
240
+ * markup via `data-command`. Call once during application startup. Idempotent and a no-op in
241
+ * a non-DOM environment. {@link Application.run} (in `@brandup/ui-app`) calls this automatically;
242
+ * call it yourself only when using `UIElement` commands without an `Application`.
243
+ *
244
+ * Making this explicit (instead of a side effect on import) lets bundlers tree-shake the
245
+ * command system away for consumers that don't use it.
246
+ */
247
+ function initUICommands() {
248
+ if (__commandsInited || typeof window === "undefined")
249
+ return;
250
+ __commandsInited = true;
251
+ window.addEventListener("click", commandClickHandler);
252
+ }
253
+ /** Remove the global command click handler registered by {@link initUICommands} (e.g. on HMR disposal or teardown). */
254
+ function destroyUI() {
255
+ if (!__commandsInited || typeof window === "undefined")
256
+ return;
257
+ __commandsInited = false;
258
+ window.removeEventListener("click", commandClickHandler);
259
+ }
260
+
261
+ exports.UIElement = UIElement;
262
+ exports.UIElementBound = UIElementBound;
263
+ exports.destroyUI = destroyUI;
264
+ exports.initUICommands = initUICommands;
265
+ //# sourceMappingURL=element.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element.js","sources":["../../../source/element.ts"],"sourcesContent":[null],"names":["EventEmitter","UICONSTANTS","trackAutoDestroy","createEffectScope","destroyUIElementsWithin","untrackAutoDestroy","disposeBindingsWithin"],"mappings":"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"}