@fluixi/dom 1.0.0-alpha.53

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 (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +657 -0
  3. package/dist/cdn/dom.cjs +1 -0
  4. package/dist/cdn/dom.d.ts +16 -0
  5. package/dist/cdn/dom.d.ts.map +1 -0
  6. package/dist/cdn/dom.global.js +1 -0
  7. package/dist/cdn/dom.js +15 -0
  8. package/dist/cdn/dom.mjs +1 -0
  9. package/dist/index.cjs +1 -0
  10. package/dist/index.d.ts +38 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +39 -0
  13. package/dist/index.mjs +1 -0
  14. package/dist/lib/component/index.cjs +1 -0
  15. package/dist/lib/component/index.d.ts +2 -0
  16. package/dist/lib/component/index.d.ts.map +1 -0
  17. package/dist/lib/component/index.js +1 -0
  18. package/dist/lib/component/index.mjs +1 -0
  19. package/dist/lib/component/modifiers.cjs +1 -0
  20. package/dist/lib/component/modifiers.d.ts +21 -0
  21. package/dist/lib/component/modifiers.d.ts.map +1 -0
  22. package/dist/lib/component/modifiers.js +171 -0
  23. package/dist/lib/component/modifiers.mjs +1 -0
  24. package/dist/lib/dom/delegate.cjs +1 -0
  25. package/dist/lib/dom/delegate.d.ts +17 -0
  26. package/dist/lib/dom/delegate.d.ts.map +1 -0
  27. package/dist/lib/dom/delegate.js +50 -0
  28. package/dist/lib/dom/delegate.mjs +1 -0
  29. package/dist/lib/dom/hydration-state.cjs +1 -0
  30. package/dist/lib/dom/hydration-state.d.ts +45 -0
  31. package/dist/lib/dom/hydration-state.d.ts.map +1 -0
  32. package/dist/lib/dom/hydration-state.js +94 -0
  33. package/dist/lib/dom/hydration-state.mjs +1 -0
  34. package/dist/lib/dom/hydration.cjs +1 -0
  35. package/dist/lib/dom/hydration.d.ts +10 -0
  36. package/dist/lib/dom/hydration.d.ts.map +1 -0
  37. package/dist/lib/dom/hydration.js +143 -0
  38. package/dist/lib/dom/hydration.mjs +1 -0
  39. package/dist/lib/dom/index.cjs +1 -0
  40. package/dist/lib/dom/index.d.ts +9 -0
  41. package/dist/lib/dom/index.d.ts.map +1 -0
  42. package/dist/lib/dom/index.js +12 -0
  43. package/dist/lib/dom/index.mjs +1 -0
  44. package/dist/lib/dom/integration.cjs +1 -0
  45. package/dist/lib/dom/integration.d.ts +16 -0
  46. package/dist/lib/dom/integration.d.ts.map +1 -0
  47. package/dist/lib/dom/integration.js +25 -0
  48. package/dist/lib/dom/integration.mjs +1 -0
  49. package/dist/lib/dom/island.cjs +1 -0
  50. package/dist/lib/dom/island.d.ts +23 -0
  51. package/dist/lib/dom/island.d.ts.map +1 -0
  52. package/dist/lib/dom/island.js +100 -0
  53. package/dist/lib/dom/island.mjs +1 -0
  54. package/dist/lib/dom/runtime.cjs +1 -0
  55. package/dist/lib/dom/runtime.d.ts +131 -0
  56. package/dist/lib/dom/runtime.d.ts.map +1 -0
  57. package/dist/lib/dom/runtime.js +1425 -0
  58. package/dist/lib/dom/runtime.mjs +1 -0
  59. package/dist/lib/dom/server/fx-data.cjs +1 -0
  60. package/dist/lib/dom/server/fx-data.d.ts +36 -0
  61. package/dist/lib/dom/server/fx-data.d.ts.map +1 -0
  62. package/dist/lib/dom/server/fx-data.js +60 -0
  63. package/dist/lib/dom/server/fx-data.mjs +1 -0
  64. package/dist/lib/dom/server/host.cjs +1 -0
  65. package/dist/lib/dom/server/host.d.ts +14 -0
  66. package/dist/lib/dom/server/host.d.ts.map +1 -0
  67. package/dist/lib/dom/server/host.js +38 -0
  68. package/dist/lib/dom/server/host.mjs +1 -0
  69. package/dist/lib/dom/server/index.cjs +1 -0
  70. package/dist/lib/dom/server/index.d.ts +13 -0
  71. package/dist/lib/dom/server/index.d.ts.map +1 -0
  72. package/dist/lib/dom/server/index.js +12 -0
  73. package/dist/lib/dom/server/index.mjs +1 -0
  74. package/dist/lib/dom/server/nodes.cjs +1 -0
  75. package/dist/lib/dom/server/nodes.d.ts +105 -0
  76. package/dist/lib/dom/server/nodes.d.ts.map +1 -0
  77. package/dist/lib/dom/server/nodes.js +304 -0
  78. package/dist/lib/dom/server/nodes.mjs +1 -0
  79. package/dist/lib/dom/server/render.cjs +1 -0
  80. package/dist/lib/dom/server/render.d.ts +45 -0
  81. package/dist/lib/dom/server/render.d.ts.map +1 -0
  82. package/dist/lib/dom/server/render.js +174 -0
  83. package/dist/lib/dom/server/render.mjs +1 -0
  84. package/dist/lib/dom/server/request-context.cjs +1 -0
  85. package/dist/lib/dom/server/request-context.d.ts +85 -0
  86. package/dist/lib/dom/server/request-context.d.ts.map +1 -0
  87. package/dist/lib/dom/server/request-context.js +122 -0
  88. package/dist/lib/dom/server/request-context.mjs +1 -0
  89. package/dist/lib/dom/server/serialize.cjs +1 -0
  90. package/dist/lib/dom/server/serialize.d.ts +2 -0
  91. package/dist/lib/dom/server/serialize.d.ts.map +1 -0
  92. package/dist/lib/dom/server/serialize.js +98 -0
  93. package/dist/lib/dom/server/serialize.mjs +1 -0
  94. package/dist/lib/dom/server-renderer.cjs +1 -0
  95. package/dist/lib/dom/server-renderer.d.ts +2 -0
  96. package/dist/lib/dom/server-renderer.d.ts.map +1 -0
  97. package/dist/lib/dom/server-renderer.js +5 -0
  98. package/dist/lib/dom/server-renderer.mjs +1 -0
  99. package/dist/lib/dom/types.cjs +1 -0
  100. package/dist/lib/dom/types.d.ts +119 -0
  101. package/dist/lib/dom/types.d.ts.map +1 -0
  102. package/dist/lib/dom/types.js +1 -0
  103. package/dist/lib/dom/types.mjs +0 -0
  104. package/dist/lib/dom/utils.cjs +1 -0
  105. package/dist/lib/dom/utils.d.ts +411 -0
  106. package/dist/lib/dom/utils.d.ts.map +1 -0
  107. package/dist/lib/dom/utils.js +746 -0
  108. package/dist/lib/dom/utils.mjs +1 -0
  109. package/dist/lib/element/index.cjs +1 -0
  110. package/dist/lib/element/index.d.ts +2 -0
  111. package/dist/lib/element/index.d.ts.map +1 -0
  112. package/dist/lib/element/index.js +1 -0
  113. package/dist/lib/element/index.mjs +0 -0
  114. package/dist/lib/element/types.cjs +1 -0
  115. package/dist/lib/element/types.d.ts +129 -0
  116. package/dist/lib/element/types.d.ts.map +1 -0
  117. package/dist/lib/element/types.js +5 -0
  118. package/dist/lib/element/types.mjs +0 -0
  119. package/dist/lib/flow/class-map.cjs +1 -0
  120. package/dist/lib/flow/class-map.d.ts +40 -0
  121. package/dist/lib/flow/class-map.d.ts.map +1 -0
  122. package/dist/lib/flow/class-map.js +44 -0
  123. package/dist/lib/flow/class-map.mjs +1 -0
  124. package/dist/lib/flow/dynamic.cjs +1 -0
  125. package/dist/lib/flow/dynamic.d.ts +27 -0
  126. package/dist/lib/flow/dynamic.d.ts.map +1 -0
  127. package/dist/lib/flow/dynamic.js +30 -0
  128. package/dist/lib/flow/dynamic.mjs +1 -0
  129. package/dist/lib/flow/error-boundary.cjs +1 -0
  130. package/dist/lib/flow/error-boundary.d.ts +34 -0
  131. package/dist/lib/flow/error-boundary.d.ts.map +1 -0
  132. package/dist/lib/flow/error-boundary.js +49 -0
  133. package/dist/lib/flow/error-boundary.mjs +1 -0
  134. package/dist/lib/flow/for.cjs +1 -0
  135. package/dist/lib/flow/for.d.ts +64 -0
  136. package/dist/lib/flow/for.d.ts.map +1 -0
  137. package/dist/lib/flow/for.js +28 -0
  138. package/dist/lib/flow/for.mjs +1 -0
  139. package/dist/lib/flow/index-flow.cjs +1 -0
  140. package/dist/lib/flow/index-flow.d.ts +34 -0
  141. package/dist/lib/flow/index-flow.d.ts.map +1 -0
  142. package/dist/lib/flow/index-flow.js +27 -0
  143. package/dist/lib/flow/index-flow.mjs +1 -0
  144. package/dist/lib/flow/index.cjs +1 -0
  145. package/dist/lib/flow/index.d.ts +20 -0
  146. package/dist/lib/flow/index.d.ts.map +1 -0
  147. package/dist/lib/flow/index.js +11 -0
  148. package/dist/lib/flow/index.mjs +1 -0
  149. package/dist/lib/flow/portal.cjs +1 -0
  150. package/dist/lib/flow/portal.d.ts +30 -0
  151. package/dist/lib/flow/portal.d.ts.map +1 -0
  152. package/dist/lib/flow/portal.js +44 -0
  153. package/dist/lib/flow/portal.mjs +1 -0
  154. package/dist/lib/flow/show.cjs +1 -0
  155. package/dist/lib/flow/show.d.ts +35 -0
  156. package/dist/lib/flow/show.d.ts.map +1 -0
  157. package/dist/lib/flow/show.js +162 -0
  158. package/dist/lib/flow/show.mjs +1 -0
  159. package/dist/lib/flow/style-map.cjs +1 -0
  160. package/dist/lib/flow/style-map.d.ts +49 -0
  161. package/dist/lib/flow/style-map.d.ts.map +1 -0
  162. package/dist/lib/flow/style-map.js +58 -0
  163. package/dist/lib/flow/style-map.mjs +1 -0
  164. package/dist/lib/flow/switch.cjs +1 -0
  165. package/dist/lib/flow/switch.d.ts +63 -0
  166. package/dist/lib/flow/switch.d.ts.map +1 -0
  167. package/dist/lib/flow/switch.js +69 -0
  168. package/dist/lib/flow/switch.mjs +1 -0
  169. package/dist/lib/flow/utils.cjs +1 -0
  170. package/dist/lib/flow/utils.d.ts +19 -0
  171. package/dist/lib/flow/utils.d.ts.map +1 -0
  172. package/dist/lib/flow/utils.js +34 -0
  173. package/dist/lib/flow/utils.mjs +1 -0
  174. package/dist/lib/index.cjs +1 -0
  175. package/dist/lib/index.d.ts +21 -0
  176. package/dist/lib/index.d.ts.map +1 -0
  177. package/dist/lib/index.js +30 -0
  178. package/dist/lib/index.mjs +1 -0
  179. package/dist/lib/jsx/control-flow/flow.cjs +1 -0
  180. package/dist/lib/jsx/control-flow/flow.d.ts +2 -0
  181. package/dist/lib/jsx/control-flow/flow.d.ts.map +1 -0
  182. package/dist/lib/jsx/control-flow/flow.js +627 -0
  183. package/dist/lib/jsx/control-flow/flow.mjs +0 -0
  184. package/dist/lib/jsx/control-flow/for-flow.cjs +1 -0
  185. package/dist/lib/jsx/control-flow/for-flow.d.ts +2 -0
  186. package/dist/lib/jsx/control-flow/for-flow.d.ts.map +1 -0
  187. package/dist/lib/jsx/control-flow/for-flow.js +1 -0
  188. package/dist/lib/jsx/control-flow/for-flow.mjs +0 -0
  189. package/dist/lib/shared/types.cjs +1 -0
  190. package/dist/lib/shared/types.d.ts +47 -0
  191. package/dist/lib/shared/types.d.ts.map +1 -0
  192. package/dist/lib/shared/types.js +1 -0
  193. package/dist/lib/shared/types.mjs +0 -0
  194. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  195. package/package.json +99 -0
@@ -0,0 +1,746 @@
1
+ /**
2
+ * @fileoverview DOM utilities for Fluixi
3
+ * @module @fluixi/core/dom
4
+ *
5
+ * This module provides utilities for DOM manipulation and queries.
6
+ */
7
+ /**
8
+ * Convert camelCase to kebab-case for attribute names
9
+ */
10
+ export function toAttributeName(key) {
11
+ // Special case for aria and data attributes
12
+ if (key.startsWith('aria-') || key.startsWith('data-')) {
13
+ return key;
14
+ }
15
+ // Handle SVG attributes with mixed case (e.g., viewBox)
16
+ if (/[A-Z]/.test(key)) {
17
+ return key.replace(/([A-Z])/g, '-$1').toLowerCase();
18
+ }
19
+ return key;
20
+ }
21
+ /**
22
+ * Check if key is an event handler
23
+ */
24
+ export function isEventHandler(key) {
25
+ return key.startsWith('@') || (key.startsWith('on') && key.length > 2);
26
+ }
27
+ /**
28
+ * Get Lit event name from key
29
+ */
30
+ export function getLitEventName(key) {
31
+ if (key.startsWith('@')) {
32
+ return key.slice(1);
33
+ }
34
+ if (key.startsWith('on')) {
35
+ const eventName = key.slice(2);
36
+ return eventName.toLowerCase();
37
+ }
38
+ return key;
39
+ }
40
+ /**
41
+ * Check if attribute should be set as property
42
+ */
43
+ export function shouldSetAsProperty(element, key) {
44
+ // These are always set as properties in Lit
45
+ const propertyAttributes = new Set([
46
+ 'checked',
47
+ 'value',
48
+ 'selected',
49
+ 'disabled',
50
+ 'readonly',
51
+ 'required',
52
+ 'multiple',
53
+ 'open',
54
+ 'contenteditable',
55
+ 'draggable',
56
+ 'spellcheck',
57
+ ]);
58
+ // These are HTML elements where certain attributes should be properties
59
+ const elementSpecificProps = {
60
+ input: new Set(['value', 'checked', 'indeterminate']),
61
+ textarea: new Set(['value']),
62
+ select: new Set(['value']),
63
+ option: new Set(['selected', 'value']),
64
+ audio: new Set(['volume', 'currentTime', 'paused']),
65
+ video: new Set(['volume', 'currentTime', 'paused']),
66
+ img: new Set(['src', 'srcset']),
67
+ iframe: new Set(['src']),
68
+ a: new Set(['href']),
69
+ };
70
+ return (propertyAttributes.has(key) ||
71
+ (elementSpecificProps[element] && elementSpecificProps[element].has(key)));
72
+ }
73
+ /**
74
+ * Self-closing HTML tags
75
+ */
76
+ export const VOID_ELEMENTS = new Set([
77
+ 'area',
78
+ 'base',
79
+ 'br',
80
+ 'col',
81
+ 'embed',
82
+ 'hr',
83
+ 'img',
84
+ 'input',
85
+ 'link',
86
+ 'meta',
87
+ 'param',
88
+ 'source',
89
+ 'track',
90
+ 'wbr',
91
+ ]);
92
+ /**
93
+ * SVG namespace elements
94
+ */
95
+ export const SVG_ELEMENTS = new Set([
96
+ 'svg',
97
+ 'g',
98
+ 'path',
99
+ 'circle',
100
+ 'rect',
101
+ 'line',
102
+ 'polyline',
103
+ 'polygon',
104
+ 'ellipse',
105
+ 'text',
106
+ 'tspan',
107
+ 'use',
108
+ 'defs',
109
+ 'symbol',
110
+ 'mask',
111
+ 'clipPath',
112
+ 'pattern',
113
+ 'linearGradient',
114
+ 'radialGradient',
115
+ 'stop',
116
+ 'filter',
117
+ 'feGaussianBlur',
118
+ 'feOffset',
119
+ 'feMerge',
120
+ 'feMergeNode',
121
+ 'foreignObject',
122
+ ]);
123
+ // SVG namespace
124
+ export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
125
+ /**
126
+ * Returns true for custom elements (tag name contains a hyphen per the Web
127
+ * Components spec — e.g. `adf-accounts-selector`).
128
+ */
129
+ export function isCustomElement(element) {
130
+ return element.localName.includes('-');
131
+ }
132
+ /**
133
+ * Set a prop on an element, choosing between DOM property assignment and
134
+ * setAttribute based on:
135
+ * - Custom elements always receive complex values (object/array) as DOM
136
+ * properties so that Lit components get the rich value, not a serialised
137
+ * string.
138
+ * - Primitives (string/number/boolean) and standard HTML elements go through
139
+ * the normal setAttribute path.
140
+ */
141
+ export function applyElementProp(element, name, value, isSVG = false) {
142
+ if (value == null) {
143
+ element.removeAttribute(name);
144
+ return;
145
+ }
146
+ // For custom elements: set objects and arrays directly as DOM properties so
147
+ // Lit (and other web-component libraries) receive the rich value.
148
+ // if(element.name === 'stocked_quantity' && name === 'value'){
149
+ // console.log(
150
+ // 'apply custom element prop',
151
+ // name,
152
+ // value,
153
+ // (element as any)[name]
154
+ // );
155
+ // }
156
+ if (isCustomElement(element)) {
157
+ // if ((element as any)[name]!==value)
158
+ element[name] = value;
159
+ return;
160
+ }
161
+ setAttribute(element, name, value, isSVG);
162
+ }
163
+ // Properties that should be set as properties, not attributes
164
+ export const JSX_PROPERTIES = new Set([
165
+ 'className',
166
+ 'value',
167
+ 'checked',
168
+ 'selected',
169
+ 'innerHTML',
170
+ 'innerText',
171
+ 'textContent',
172
+ 'indeterminate',
173
+ 'htmlFor',
174
+ ]);
175
+ /**
176
+ * Queries a single element with type safety
177
+ *
178
+ * @template T - Element type
179
+ * @param {string} selector - CSS selector
180
+ * @param {Element | Document} root - Root element (default: document)
181
+ * @returns {T | null} Found element or null
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * import { query } from '@fluixi/core/dom';
186
+ *
187
+ * const button = query<HTMLButtonElement>('#my-button');
188
+ * button?.click();
189
+ * ```
190
+ */
191
+ export function query(selector, root = document) {
192
+ return root.querySelector(selector);
193
+ }
194
+ /**
195
+ * Queries all matching elements with type safety
196
+ *
197
+ * @template T - Element type
198
+ * @param {string} selector - CSS selector
199
+ * @param {Element | Document} root - Root element (default: document)
200
+ * @returns {T[]} Array of matching elements
201
+ *
202
+ * @example
203
+ * ```typescript
204
+ * import { queryAll } from '@fluixi/core/dom';
205
+ *
206
+ * const buttons = queryAll<HTMLButtonElement>('button');
207
+ * buttons.forEach(btn => btn.disabled = true);
208
+ * ```
209
+ */
210
+ export function queryAll(selector, root = document) {
211
+ return Array.from(root.querySelectorAll(selector));
212
+ }
213
+ /**
214
+ * Queries the closest matching ancestor element
215
+ *
216
+ * @template T - Element type
217
+ * @param {Element} element - Starting element
218
+ * @param {string} selector - CSS selector
219
+ * @returns {T | null} Closest matching ancestor or null
220
+ *
221
+ * @example
222
+ * ```typescript
223
+ * import { closest } from '@fluixi/core/dom';
224
+ *
225
+ * const form = closest<HTMLFormElement>(button, 'form');
226
+ * form?.submit();
227
+ * ```
228
+ */
229
+ export function closest(element, selector) {
230
+ return element.closest(selector);
231
+ }
232
+ /**
233
+ * Adds one or more classes to an element
234
+ *
235
+ * @param {Element} element - Target element
236
+ * @param {...string} classes - Classes to add
237
+ *
238
+ * @example
239
+ * ```typescript
240
+ * import { addClass } from '@fluixi/core/dom';
241
+ *
242
+ * addClass(element, 'active', 'visible');
243
+ * ```
244
+ */
245
+ export function addClass(element, ...classes) {
246
+ element.classList.add(...classes);
247
+ }
248
+ /**
249
+ * Removes one or more classes from an element
250
+ *
251
+ * @param {Element} element - Target element
252
+ * @param {...string} classes - Classes to remove
253
+ *
254
+ * @example
255
+ * ```typescript
256
+ * import { removeClass } from '@fluixi/core/dom';
257
+ *
258
+ * removeClass(element, 'active', 'visible');
259
+ * ```
260
+ */
261
+ export function removeClass(element, ...classes) {
262
+ element.classList.remove(...classes);
263
+ }
264
+ /**
265
+ * Toggles one or more classes on an element
266
+ *
267
+ * @param {Element} element - Target element
268
+ * @param {...string} classes - Classes to toggle
269
+ *
270
+ * @example
271
+ * ```typescript
272
+ * import { toggleClass } from '@fluixi/core/dom';
273
+ *
274
+ * toggleClass(element, 'active');
275
+ * ```
276
+ */
277
+ export function toggleClass(element, ...classes) {
278
+ classes.forEach((cls) => element.classList.toggle(cls));
279
+ }
280
+ /**
281
+ * Checks if element has a class
282
+ *
283
+ * @param {Element} element - Target element
284
+ * @param {string} className - Class name to check
285
+ * @returns {boolean} True if element has the class
286
+ *
287
+ * @example
288
+ * ```typescript
289
+ * import { hasClass } from '@fluixi/core/dom';
290
+ *
291
+ * if (hasClass(element, 'active')) {
292
+ * console.log('Element is active');
293
+ * }
294
+ * ```
295
+ */
296
+ export function hasClass(element, className) {
297
+ return element.classList.contains(className);
298
+ }
299
+ /**
300
+ * Sets an attribute on an element
301
+ *
302
+ * @param {Element} element - Target element
303
+ * @param {string} name - Attribute name
304
+ * @param {string | number | boolean} value - Attribute value
305
+ *
306
+ * @example
307
+ * ```typescript
308
+ * import { setAttribute } from '@fluixi/core/dom';
309
+ *
310
+ * setAttribute(input, 'disabled', true);
311
+ * setAttribute(div, 'data-id', '123');
312
+ * ```
313
+ */
314
+ export function setAttribute(element, name, value, isSVG = false) {
315
+ // null/undefined/false REMOVE the attribute (a conditionally-absent attribute must be omitted,
316
+ // not rendered as ="undefined"/"false"); true renders as the empty string. Matches Solid's
317
+ // dom-expressions semantics.
318
+ if (value == null || value === false) {
319
+ element.removeAttribute(name);
320
+ return;
321
+ }
322
+ if (value === true) {
323
+ element.setAttribute(name, '');
324
+ return;
325
+ }
326
+ if (typeof value === 'object') {
327
+ element.setAttribute(name, JSON.stringify(value));
328
+ return;
329
+ }
330
+ element.setAttribute(name, String(value));
331
+ }
332
+ // /**
333
+ // * Set an attribute or property on an element
334
+ // */
335
+ // export function setAttribute(
336
+ // element: Element,
337
+ // name: string,
338
+ // value: any,
339
+ // isSVG = false
340
+ // ): void {
341
+ // if (value == null || value === false) {
342
+ // element.removeAttribute(name);
343
+ // return;
344
+ // }
345
+ // if (value === true) {
346
+ // element.setAttribute(name, '');
347
+ // return;
348
+ // }
349
+ // element.setAttribute(name, String(value));
350
+ // }
351
+ /**
352
+ * Gets an attribute from an element
353
+ *
354
+ * @param {Element} element - Target element
355
+ * @param {string} name - Attribute name
356
+ * @returns {string | null} Attribute value or null
357
+ *
358
+ * @example
359
+ * ```typescript
360
+ * import { getAttribute } from '@fluixi/core/dom';
361
+ *
362
+ * const id = getAttribute(div, 'data-id');
363
+ * ```
364
+ */
365
+ export function getAttribute(element, name) {
366
+ return element.getAttribute(name);
367
+ }
368
+ /**
369
+ * Removes an attribute from an element
370
+ *
371
+ * @param {Element} element - Target element
372
+ * @param {string} name - Attribute name
373
+ *
374
+ * @example
375
+ * ```typescript
376
+ * import { removeAttribute } from '@fluixi/core/dom';
377
+ *
378
+ * removeAttribute(input, 'disabled');
379
+ * ```
380
+ */
381
+ export function removeAttribute(element, name) {
382
+ element.removeAttribute(name);
383
+ }
384
+ /**
385
+ * Sets multiple styles on an element
386
+ *
387
+ * @param {HTMLElement} element - Target element
388
+ * @param {Partial<CSSStyleDeclaration>} styles - Styles object
389
+ *
390
+ * @example
391
+ * ```typescript
392
+ * import { setStyles } from '@fluixi/core/dom';
393
+ *
394
+ * setStyles(div, {
395
+ * backgroundColor: 'red',
396
+ * padding: '10px',
397
+ * display: 'flex'
398
+ * });
399
+ * ```
400
+ */
401
+ export function setStyles(element, styles) {
402
+ Object.assign(element.style, styles);
403
+ }
404
+ /**
405
+ * Gets computed style of an element
406
+ *
407
+ * @param {Element} element - Target element
408
+ * @param {string} property - CSS property name
409
+ * @returns {string} Computed style value
410
+ *
411
+ * @example
412
+ * ```typescript
413
+ * import { getStyle } from '@fluixi/core/dom';
414
+ *
415
+ * const color = getStyle(div, 'backgroundColor');
416
+ * ```
417
+ */
418
+ export function getStyle(element, property) {
419
+ return window.getComputedStyle(element).getPropertyValue(property);
420
+ }
421
+ /**
422
+ * Creates an element with attributes and children
423
+ *
424
+ * @template K - Element tag name
425
+ * @param {K} tagName - HTML tag name
426
+ * @param {Record<string, any>} attributes - Element attributes
427
+ * @param {...(Node | string)} children - Child nodes or text
428
+ * @returns {HTMLElementTagNameMap[K]} Created element
429
+ *
430
+ * @example
431
+ * ```typescript
432
+ * import { createElement } from '@fluixi/core/dom';
433
+ *
434
+ * const button = createElement('button',
435
+ * { class: 'btn', disabled: true },
436
+ * 'Click me'
437
+ * );
438
+ * ```
439
+ */
440
+ export function createElement(tagName, attributes = {}, ...children) {
441
+ const element = document.createElement(tagName);
442
+ Object.entries(attributes).forEach(([key, value]) => {
443
+ if (key === 'class' || key === 'className') {
444
+ element.className = value;
445
+ }
446
+ else if (key === 'style' && typeof value === 'object') {
447
+ setStyles(element, value);
448
+ }
449
+ else if (key.startsWith('on') && typeof value === 'function') {
450
+ const event = key.slice(2).toLowerCase();
451
+ element.addEventListener(event, value);
452
+ }
453
+ else {
454
+ setAttribute(element, key, value);
455
+ }
456
+ });
457
+ children.forEach((child) => {
458
+ if (typeof child === 'string') {
459
+ element.appendChild(document.createTextNode(child));
460
+ }
461
+ else {
462
+ element.appendChild(child);
463
+ }
464
+ });
465
+ return element;
466
+ }
467
+ /**
468
+ * Removes all children from an element
469
+ *
470
+ * @param {Element} element - Target element
471
+ *
472
+ * @example
473
+ * ```typescript
474
+ * import { removeChildren } from '@fluixi/core/dom';
475
+ *
476
+ * removeChildren(container);
477
+ * ```
478
+ */
479
+ export function removeChildren(element) {
480
+ while (element.firstChild) {
481
+ element.removeChild(element.firstChild);
482
+ }
483
+ }
484
+ /**
485
+ * Appends multiple children to an element
486
+ *
487
+ * @param {Element} parent - Parent element
488
+ * @param {...(Node | string)} children - Children to append
489
+ *
490
+ * @example
491
+ * ```typescript
492
+ * import { appendChildren } from '@fluixi/core/dom';
493
+ *
494
+ * appendChildren(container,
495
+ * createElement('div', {}, 'Child 1'),
496
+ * 'Text node',
497
+ * createElement('span', {}, 'Child 2')
498
+ * );
499
+ * ```
500
+ */
501
+ export function appendChildren(parent, ...children) {
502
+ children.forEach((child) => {
503
+ if (typeof child === 'string') {
504
+ parent.appendChild(document.createTextNode(child));
505
+ }
506
+ else {
507
+ parent.appendChild(child);
508
+ }
509
+ });
510
+ }
511
+ /**
512
+ * Checks if element is visible in the viewport
513
+ *
514
+ * @param {Element} element - Target element
515
+ * @returns {boolean} True if element is visible
516
+ *
517
+ * @example
518
+ * ```typescript
519
+ * import { isVisible } from '@fluixi/core/dom';
520
+ *
521
+ * if (isVisible(element)) {
522
+ * console.log('Element is in viewport');
523
+ * }
524
+ * ```
525
+ */
526
+ export function isVisible(element) {
527
+ const rect = element.getBoundingClientRect();
528
+ return (rect.top >= 0 &&
529
+ rect.left >= 0 &&
530
+ rect.bottom <= window.innerHeight &&
531
+ rect.right <= window.innerWidth);
532
+ }
533
+ /**
534
+ * Scrolls element into view smoothly
535
+ *
536
+ * @param {Element} element - Target element
537
+ * @param {ScrollIntoViewOptions} options - Scroll options
538
+ *
539
+ * @example
540
+ * ```typescript
541
+ * import { scrollIntoView } from '@fluixi/core/dom';
542
+ *
543
+ * scrollIntoView(element, { behavior: 'smooth', block: 'center' });
544
+ * ```
545
+ */
546
+ export function scrollIntoView(element, options = { behavior: 'smooth', block: 'nearest' }) {
547
+ element.scrollIntoView(options);
548
+ }
549
+ /**
550
+ * Gets element's offset relative to document
551
+ *
552
+ * @param {Element} element - Target element
553
+ * @returns {{ top: number; left: number }} Offset coordinates
554
+ *
555
+ * @example
556
+ * ```typescript
557
+ * import { getOffset } from '@fluixi/core/dom';
558
+ *
559
+ * const { top, left } = getOffset(element);
560
+ * ```
561
+ */
562
+ export function getOffset(element) {
563
+ const rect = element.getBoundingClientRect();
564
+ return {
565
+ top: rect.top + window.pageYOffset,
566
+ left: rect.left + window.pageXOffset,
567
+ };
568
+ }
569
+ /**
570
+ * Delegates event handling to a parent element
571
+ *
572
+ * @template K - Event type
573
+ * @param {Element} parent - Parent element
574
+ * @param {K} eventName - Event name
575
+ * @param {string} selector - Child selector
576
+ * @param {(event: Event, target: Element) => void} handler - Event handler
577
+ * @returns {() => void} Cleanup function
578
+ *
579
+ * @example
580
+ * ```typescript
581
+ * import { delegate } from '@fluixi/core/dom';
582
+ *
583
+ * const cleanup = delegate(list, 'click', '.item', (event, target) => {
584
+ * console.log('Clicked item:', target);
585
+ * });
586
+ * ```
587
+ */
588
+ export function delegate(parent, eventName, selector, handler) {
589
+ const listener = (event) => {
590
+ const target = event.target.closest(selector);
591
+ if (target && parent.contains(target)) {
592
+ handler(event, target);
593
+ }
594
+ };
595
+ parent.addEventListener(eventName, listener);
596
+ return () => {
597
+ parent.removeEventListener(eventName, listener);
598
+ };
599
+ }
600
+ /**
601
+ * Waits for an element to appear in the DOM
602
+ *
603
+ * @param {string} selector - CSS selector
604
+ * @param {number} timeout - Timeout in milliseconds (default: 5000)
605
+ * @returns {Promise<Element>} Promise that resolves with the element
606
+ *
607
+ * @example
608
+ * ```typescript
609
+ * import { waitForElement } from '@fluixi/core/dom';
610
+ *
611
+ * const element = await waitForElement('#dynamic-content');
612
+ * ```
613
+ */
614
+ export function waitForElement(selector, timeout = 5000) {
615
+ return new Promise((resolve, reject) => {
616
+ const element = query(selector);
617
+ if (element) {
618
+ resolve(element);
619
+ return;
620
+ }
621
+ const observer = new MutationObserver(() => {
622
+ const element = query(selector);
623
+ if (element) {
624
+ observer.disconnect();
625
+ clearTimeout(timeoutId);
626
+ resolve(element);
627
+ }
628
+ });
629
+ observer.observe(document.body, {
630
+ childList: true,
631
+ subtree: true,
632
+ });
633
+ const timeoutId = setTimeout(() => {
634
+ observer.disconnect();
635
+ reject(new Error(`Element ${selector} not found within ${timeout}ms`));
636
+ }, timeout);
637
+ });
638
+ }
639
+ /**
640
+ * Observes element intersection with viewport
641
+ *
642
+ * @param {Element} element - Target element
643
+ * @param {(isIntersecting: boolean) => void} callback - Callback function
644
+ * @param {IntersectionObserverInit} options - Observer options
645
+ * @returns {() => void} Cleanup function
646
+ *
647
+ * @example
648
+ * ```typescript
649
+ * import { observeIntersection } from '@fluixi/core/dom';
650
+ *
651
+ * const cleanup = observeIntersection(element, (isIntersecting) => {
652
+ * console.log('Element visible:', isIntersecting);
653
+ * });
654
+ * ```
655
+ */
656
+ export function observeIntersection(element, callback, options = {}) {
657
+ const observer = new IntersectionObserver((entries) => {
658
+ entries.forEach((entry) => {
659
+ callback(entry.isIntersecting);
660
+ });
661
+ }, options);
662
+ observer.observe(element);
663
+ return () => {
664
+ observer.disconnect();
665
+ };
666
+ }
667
+ /**
668
+ * Observes element size changes
669
+ *
670
+ * @param {Element} element - Target element
671
+ * @param {(entry: ResizeObserverEntry) => void} callback - Callback function
672
+ * @returns {() => void} Cleanup function
673
+ *
674
+ * @example
675
+ * ```typescript
676
+ * import { observeResize } from '@fluixi/core/dom';
677
+ *
678
+ * const cleanup = observeResize(element, (entry) => {
679
+ * console.log('New size:', entry.contentRect.width, entry.contentRect.height);
680
+ * });
681
+ * ```
682
+ */
683
+ export function observeResize(element, callback) {
684
+ const observer = new ResizeObserver((entries) => {
685
+ entries.forEach(callback);
686
+ });
687
+ observer.observe(element);
688
+ return () => {
689
+ observer.disconnect();
690
+ };
691
+ }
692
+ const REACT_ELEMENT_TYPE = typeof Symbol === 'function' && Symbol.for
693
+ ? Symbol.for('react.element')
694
+ : 0xeac7;
695
+ export function isReactElement(value) {
696
+ return (typeof value === 'object' &&
697
+ value !== null &&
698
+ value.$$typeof === REACT_ELEMENT_TYPE);
699
+ }
700
+ export function isLitTemplateResult(value) {
701
+ return (value &&
702
+ typeof value === 'object' &&
703
+ value.strings &&
704
+ Array.isArray(value.strings) &&
705
+ value.values &&
706
+ Array.isArray(value.values));
707
+ }
708
+ export function isTemplateFactory(value) {
709
+ if (typeof value !== 'function')
710
+ return false;
711
+ try {
712
+ const result = value();
713
+ return isLitTemplateResult(result);
714
+ }
715
+ catch {
716
+ return false;
717
+ }
718
+ }
719
+ export function isJSXTemplate(value) {
720
+ return (typeof value === 'object' &&
721
+ value !== null &&
722
+ (value.$$typeof === 'template' || typeof value.type === 'function'));
723
+ }
724
+ export function isDOMNode(value) {
725
+ return value instanceof Node;
726
+ }
727
+ export function registerGlobal(key, value, _Key = 'Fluixi') {
728
+ if (typeof globalThis !== 'undefined') {
729
+ if (typeof globalThis[_Key] === 'undefined') {
730
+ globalThis[_Key] = {};
731
+ }
732
+ if (typeof globalThis[_Key][key] === 'undefined') {
733
+ globalThis[_Key][key] = value;
734
+ }
735
+ }
736
+ }
737
+ export function getGlobal(key, value, _Key = 'Fluixi') {
738
+ if (typeof globalThis !== 'undefined') {
739
+ if (typeof globalThis[_Key] !== 'undefined') {
740
+ if (typeof globalThis[_Key][key] !== 'undefined') {
741
+ return globalThis[_Key][key];
742
+ }
743
+ }
744
+ }
745
+ return value;
746
+ }