@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.
- package/LICENSE +21 -0
- package/README.md +657 -0
- package/dist/cdn/dom.cjs +1 -0
- package/dist/cdn/dom.d.ts +16 -0
- package/dist/cdn/dom.d.ts.map +1 -0
- package/dist/cdn/dom.global.js +1 -0
- package/dist/cdn/dom.js +15 -0
- package/dist/cdn/dom.mjs +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.mjs +1 -0
- package/dist/lib/component/index.cjs +1 -0
- package/dist/lib/component/index.d.ts +2 -0
- package/dist/lib/component/index.d.ts.map +1 -0
- package/dist/lib/component/index.js +1 -0
- package/dist/lib/component/index.mjs +1 -0
- package/dist/lib/component/modifiers.cjs +1 -0
- package/dist/lib/component/modifiers.d.ts +21 -0
- package/dist/lib/component/modifiers.d.ts.map +1 -0
- package/dist/lib/component/modifiers.js +171 -0
- package/dist/lib/component/modifiers.mjs +1 -0
- package/dist/lib/dom/delegate.cjs +1 -0
- package/dist/lib/dom/delegate.d.ts +17 -0
- package/dist/lib/dom/delegate.d.ts.map +1 -0
- package/dist/lib/dom/delegate.js +50 -0
- package/dist/lib/dom/delegate.mjs +1 -0
- package/dist/lib/dom/hydration-state.cjs +1 -0
- package/dist/lib/dom/hydration-state.d.ts +45 -0
- package/dist/lib/dom/hydration-state.d.ts.map +1 -0
- package/dist/lib/dom/hydration-state.js +94 -0
- package/dist/lib/dom/hydration-state.mjs +1 -0
- package/dist/lib/dom/hydration.cjs +1 -0
- package/dist/lib/dom/hydration.d.ts +10 -0
- package/dist/lib/dom/hydration.d.ts.map +1 -0
- package/dist/lib/dom/hydration.js +143 -0
- package/dist/lib/dom/hydration.mjs +1 -0
- package/dist/lib/dom/index.cjs +1 -0
- package/dist/lib/dom/index.d.ts +9 -0
- package/dist/lib/dom/index.d.ts.map +1 -0
- package/dist/lib/dom/index.js +12 -0
- package/dist/lib/dom/index.mjs +1 -0
- package/dist/lib/dom/integration.cjs +1 -0
- package/dist/lib/dom/integration.d.ts +16 -0
- package/dist/lib/dom/integration.d.ts.map +1 -0
- package/dist/lib/dom/integration.js +25 -0
- package/dist/lib/dom/integration.mjs +1 -0
- package/dist/lib/dom/island.cjs +1 -0
- package/dist/lib/dom/island.d.ts +23 -0
- package/dist/lib/dom/island.d.ts.map +1 -0
- package/dist/lib/dom/island.js +100 -0
- package/dist/lib/dom/island.mjs +1 -0
- package/dist/lib/dom/runtime.cjs +1 -0
- package/dist/lib/dom/runtime.d.ts +131 -0
- package/dist/lib/dom/runtime.d.ts.map +1 -0
- package/dist/lib/dom/runtime.js +1425 -0
- package/dist/lib/dom/runtime.mjs +1 -0
- package/dist/lib/dom/server/fx-data.cjs +1 -0
- package/dist/lib/dom/server/fx-data.d.ts +36 -0
- package/dist/lib/dom/server/fx-data.d.ts.map +1 -0
- package/dist/lib/dom/server/fx-data.js +60 -0
- package/dist/lib/dom/server/fx-data.mjs +1 -0
- package/dist/lib/dom/server/host.cjs +1 -0
- package/dist/lib/dom/server/host.d.ts +14 -0
- package/dist/lib/dom/server/host.d.ts.map +1 -0
- package/dist/lib/dom/server/host.js +38 -0
- package/dist/lib/dom/server/host.mjs +1 -0
- package/dist/lib/dom/server/index.cjs +1 -0
- package/dist/lib/dom/server/index.d.ts +13 -0
- package/dist/lib/dom/server/index.d.ts.map +1 -0
- package/dist/lib/dom/server/index.js +12 -0
- package/dist/lib/dom/server/index.mjs +1 -0
- package/dist/lib/dom/server/nodes.cjs +1 -0
- package/dist/lib/dom/server/nodes.d.ts +105 -0
- package/dist/lib/dom/server/nodes.d.ts.map +1 -0
- package/dist/lib/dom/server/nodes.js +304 -0
- package/dist/lib/dom/server/nodes.mjs +1 -0
- package/dist/lib/dom/server/render.cjs +1 -0
- package/dist/lib/dom/server/render.d.ts +45 -0
- package/dist/lib/dom/server/render.d.ts.map +1 -0
- package/dist/lib/dom/server/render.js +174 -0
- package/dist/lib/dom/server/render.mjs +1 -0
- package/dist/lib/dom/server/request-context.cjs +1 -0
- package/dist/lib/dom/server/request-context.d.ts +85 -0
- package/dist/lib/dom/server/request-context.d.ts.map +1 -0
- package/dist/lib/dom/server/request-context.js +122 -0
- package/dist/lib/dom/server/request-context.mjs +1 -0
- package/dist/lib/dom/server/serialize.cjs +1 -0
- package/dist/lib/dom/server/serialize.d.ts +2 -0
- package/dist/lib/dom/server/serialize.d.ts.map +1 -0
- package/dist/lib/dom/server/serialize.js +98 -0
- package/dist/lib/dom/server/serialize.mjs +1 -0
- package/dist/lib/dom/server-renderer.cjs +1 -0
- package/dist/lib/dom/server-renderer.d.ts +2 -0
- package/dist/lib/dom/server-renderer.d.ts.map +1 -0
- package/dist/lib/dom/server-renderer.js +5 -0
- package/dist/lib/dom/server-renderer.mjs +1 -0
- package/dist/lib/dom/types.cjs +1 -0
- package/dist/lib/dom/types.d.ts +119 -0
- package/dist/lib/dom/types.d.ts.map +1 -0
- package/dist/lib/dom/types.js +1 -0
- package/dist/lib/dom/types.mjs +0 -0
- package/dist/lib/dom/utils.cjs +1 -0
- package/dist/lib/dom/utils.d.ts +411 -0
- package/dist/lib/dom/utils.d.ts.map +1 -0
- package/dist/lib/dom/utils.js +746 -0
- package/dist/lib/dom/utils.mjs +1 -0
- package/dist/lib/element/index.cjs +1 -0
- package/dist/lib/element/index.d.ts +2 -0
- package/dist/lib/element/index.d.ts.map +1 -0
- package/dist/lib/element/index.js +1 -0
- package/dist/lib/element/index.mjs +0 -0
- package/dist/lib/element/types.cjs +1 -0
- package/dist/lib/element/types.d.ts +129 -0
- package/dist/lib/element/types.d.ts.map +1 -0
- package/dist/lib/element/types.js +5 -0
- package/dist/lib/element/types.mjs +0 -0
- package/dist/lib/flow/class-map.cjs +1 -0
- package/dist/lib/flow/class-map.d.ts +40 -0
- package/dist/lib/flow/class-map.d.ts.map +1 -0
- package/dist/lib/flow/class-map.js +44 -0
- package/dist/lib/flow/class-map.mjs +1 -0
- package/dist/lib/flow/dynamic.cjs +1 -0
- package/dist/lib/flow/dynamic.d.ts +27 -0
- package/dist/lib/flow/dynamic.d.ts.map +1 -0
- package/dist/lib/flow/dynamic.js +30 -0
- package/dist/lib/flow/dynamic.mjs +1 -0
- package/dist/lib/flow/error-boundary.cjs +1 -0
- package/dist/lib/flow/error-boundary.d.ts +34 -0
- package/dist/lib/flow/error-boundary.d.ts.map +1 -0
- package/dist/lib/flow/error-boundary.js +49 -0
- package/dist/lib/flow/error-boundary.mjs +1 -0
- package/dist/lib/flow/for.cjs +1 -0
- package/dist/lib/flow/for.d.ts +64 -0
- package/dist/lib/flow/for.d.ts.map +1 -0
- package/dist/lib/flow/for.js +28 -0
- package/dist/lib/flow/for.mjs +1 -0
- package/dist/lib/flow/index-flow.cjs +1 -0
- package/dist/lib/flow/index-flow.d.ts +34 -0
- package/dist/lib/flow/index-flow.d.ts.map +1 -0
- package/dist/lib/flow/index-flow.js +27 -0
- package/dist/lib/flow/index-flow.mjs +1 -0
- package/dist/lib/flow/index.cjs +1 -0
- package/dist/lib/flow/index.d.ts +20 -0
- package/dist/lib/flow/index.d.ts.map +1 -0
- package/dist/lib/flow/index.js +11 -0
- package/dist/lib/flow/index.mjs +1 -0
- package/dist/lib/flow/portal.cjs +1 -0
- package/dist/lib/flow/portal.d.ts +30 -0
- package/dist/lib/flow/portal.d.ts.map +1 -0
- package/dist/lib/flow/portal.js +44 -0
- package/dist/lib/flow/portal.mjs +1 -0
- package/dist/lib/flow/show.cjs +1 -0
- package/dist/lib/flow/show.d.ts +35 -0
- package/dist/lib/flow/show.d.ts.map +1 -0
- package/dist/lib/flow/show.js +162 -0
- package/dist/lib/flow/show.mjs +1 -0
- package/dist/lib/flow/style-map.cjs +1 -0
- package/dist/lib/flow/style-map.d.ts +49 -0
- package/dist/lib/flow/style-map.d.ts.map +1 -0
- package/dist/lib/flow/style-map.js +58 -0
- package/dist/lib/flow/style-map.mjs +1 -0
- package/dist/lib/flow/switch.cjs +1 -0
- package/dist/lib/flow/switch.d.ts +63 -0
- package/dist/lib/flow/switch.d.ts.map +1 -0
- package/dist/lib/flow/switch.js +69 -0
- package/dist/lib/flow/switch.mjs +1 -0
- package/dist/lib/flow/utils.cjs +1 -0
- package/dist/lib/flow/utils.d.ts +19 -0
- package/dist/lib/flow/utils.d.ts.map +1 -0
- package/dist/lib/flow/utils.js +34 -0
- package/dist/lib/flow/utils.mjs +1 -0
- package/dist/lib/index.cjs +1 -0
- package/dist/lib/index.d.ts +21 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +30 -0
- package/dist/lib/index.mjs +1 -0
- package/dist/lib/jsx/control-flow/flow.cjs +1 -0
- package/dist/lib/jsx/control-flow/flow.d.ts +2 -0
- package/dist/lib/jsx/control-flow/flow.d.ts.map +1 -0
- package/dist/lib/jsx/control-flow/flow.js +627 -0
- package/dist/lib/jsx/control-flow/flow.mjs +0 -0
- package/dist/lib/jsx/control-flow/for-flow.cjs +1 -0
- package/dist/lib/jsx/control-flow/for-flow.d.ts +2 -0
- package/dist/lib/jsx/control-flow/for-flow.d.ts.map +1 -0
- package/dist/lib/jsx/control-flow/for-flow.js +1 -0
- package/dist/lib/jsx/control-flow/for-flow.mjs +0 -0
- package/dist/lib/shared/types.cjs +1 -0
- package/dist/lib/shared/types.d.ts +47 -0
- package/dist/lib/shared/types.d.ts.map +1 -0
- package/dist/lib/shared/types.js +1 -0
- package/dist/lib/shared/types.mjs +0 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- 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
|
+
}
|