@trunkjs/browser-utils 1.0.50 → 1.0.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/.ai-usage-info.md +3 -2
- package/CHANGELOG.md +32 -0
- package/README.md +20 -0
- package/index.d.ts +1 -0
- package/index.js +425 -247
- package/lib/FormDataAccessor.d.ts +28 -0
- package/package.json +2 -2
- package/skills/browser-utils-usage/SKILL.md +35 -0
- package/skills/browser-utils-usage/references/custom-elements-and-mixins.md +175 -0
- package/skills/browser-utils-usage/references/helpers-and-storage.md +159 -0
- package/skills/form-data-accessor-usage/SKILL.md +53 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type FormDataAccessorData = Record<string, unknown>;
|
|
2
|
+
export type FormDataValueElement = HTMLElement & {
|
|
3
|
+
value: unknown;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
name?: string;
|
|
6
|
+
};
|
|
7
|
+
export interface FormDataAccessorEntry {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly element: FormDataValueElement;
|
|
10
|
+
value: unknown;
|
|
11
|
+
}
|
|
12
|
+
/** Dynamically reads and writes named value elements below a DOM root. */
|
|
13
|
+
export declare class FormDataAccessor {
|
|
14
|
+
readonly root: ParentNode;
|
|
15
|
+
constructor(root: ParentNode);
|
|
16
|
+
get entries(): FormDataAccessorEntry[];
|
|
17
|
+
get data(): FormDataAccessorData;
|
|
18
|
+
set data(data: FormDataAccessorData);
|
|
19
|
+
get formData(): FormData;
|
|
20
|
+
private get groupedEntries();
|
|
21
|
+
private getName;
|
|
22
|
+
private isValueElement;
|
|
23
|
+
/** A named value element owns its value, so its descendants are not collected twice. */
|
|
24
|
+
private hasValueElementParent;
|
|
25
|
+
private isDisabled;
|
|
26
|
+
private readGroup;
|
|
27
|
+
private writeGroup;
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trunkjs/browser-utils",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.53",
|
|
4
4
|
"main": "./index.js",
|
|
5
5
|
"repository": {
|
|
6
6
|
"directory": "packages/browser-utils",
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "https://github.com/trunkjs/trunkjs-monorepo"
|
|
8
|
+
"url": "git+https://github.com/trunkjs/trunkjs-monorepo.git"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {},
|
|
11
11
|
"type": "module",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: browser-utils-usage
|
|
3
|
+
description: Use @trunkjs/browser-utils for browser-side DOM creation, form values, timing, storage, breakpoints, typed event listeners, logging, loader coordination, and Lit/custom-element mixins. Do not use it for server-only code.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Browser Utils Usage
|
|
7
|
+
|
|
8
|
+
Prefer the public exports from `@trunkjs/browser-utils` over local copies of equivalent browser helpers. Import from the package root; internal `src/` paths are not public API.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { create_element, local_storage, waitForLoad } from '@trunkjs/browser-utils';
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Choose the smallest API that matches the job:
|
|
15
|
+
|
|
16
|
+
- DOM nodes: `create_element`
|
|
17
|
+
- Named form values: `FormDataAccessor`
|
|
18
|
+
- Burst control: `Debouncer` or `@debounce`
|
|
19
|
+
- Browser lifecycle and events: `waitFor*`, `sleep`
|
|
20
|
+
- JSON-like browser state: `local_storage`, `session_storage`
|
|
21
|
+
- Breakpoint inspection: `breakpoints`, `getCurrentBreakpoint`, `getBreakpointMinWidth`
|
|
22
|
+
- Custom elements: `EventBindingsMixin`, `LoggingMixin`, `BreakPointMixin`
|
|
23
|
+
- Lit elements: `LoaderMixin`, `SlotVisibilityMixin`
|
|
24
|
+
|
|
25
|
+
Read [references/helpers-and-storage.md](references/helpers-and-storage.md) for DOM, timing, storage, diagnostics, and breakpoint examples. Read [references/custom-elements-and-mixins.md](references/custom-elements-and-mixins.md) when implementing custom elements, Lit components, decorators, loader coordination, or slot handling.
|
|
26
|
+
|
|
27
|
+
For detailed form-value examples, use the focused `form-data-accessor-usage` package skill.
|
|
28
|
+
|
|
29
|
+
## Package-specific constraints
|
|
30
|
+
|
|
31
|
+
- Use these APIs only where browser globals are available. Storage proxies tolerate SSR by remaining in memory, but DOM and lifecycle helpers require the browser.
|
|
32
|
+
- Treat storage values as JSON data. Functions, class instances, symbols, and cyclic objects are not supported.
|
|
33
|
+
- Let the event mixin own listener cleanup; do not add duplicate manual listeners for the same decorator.
|
|
34
|
+
- Use loader-aware waits only for visual startup coordination, not as a replacement for application data loading.
|
|
35
|
+
- Preserve native error behavior: for example, `getBreakpointMinWidth` throws for unknown names and `waitForLoad(image)` rejects when the image fails.
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Custom Elements and Mixins
|
|
2
|
+
|
|
3
|
+
Use these APIs for lifecycle-aware browser components. Preserve each superclass lifecycle call when overriding `connectedCallback`, `disconnectedCallback`, or `firstUpdated`.
|
|
4
|
+
|
|
5
|
+
## Bind and clean up events
|
|
6
|
+
|
|
7
|
+
`EventBindingsMixin` registers decorated methods on connection and removes every registered listener through an internal `AbortController` on disconnection.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { EventBindingsMixin, Listen } from '@trunkjs/browser-utils';
|
|
11
|
+
|
|
12
|
+
class SearchBox extends EventBindingsMixin(HTMLElement) {
|
|
13
|
+
@Listen('input', { target: 'host' })
|
|
14
|
+
onInput(event: InputEvent): void {
|
|
15
|
+
const input = event.target as HTMLInputElement;
|
|
16
|
+
this.dispatchEvent(new CustomEvent('search', { detail: input.value }));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@Listen('resize', { target: 'window', options: { passive: true } })
|
|
20
|
+
onResize(): void {
|
|
21
|
+
this.toggleAttribute('compact', window.innerWidth < 768);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
customElements.define('search-box', SearchBox);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Available targets are `host`, `document`, `window`, `shadowRoot`, an `EventTarget`, or a callback receiving the host. `shadowRoot` falls back to the host when no shadow root exists.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
class ShortcutPanel extends EventBindingsMixin(HTMLElement) {
|
|
32
|
+
@Listen(['keydown', 'keyup'], { target: 'document' })
|
|
33
|
+
onKey(event: KeyboardEvent): void {
|
|
34
|
+
this.toggleAttribute('modifier-active', event.ctrlKey || event.metaKey);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Listen('click', { target: (host) => host.querySelector('button') ?? host })
|
|
38
|
+
onAction(): void {
|
|
39
|
+
this.dispatchEvent(new Event('action'));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Add application-specific events to `DocumentEventMap` to retain decorator inference:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
declare global {
|
|
48
|
+
interface DocumentEventMap {
|
|
49
|
+
'cart:updated': CustomEvent<{ itemCount: number }>;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
class CartBadge extends EventBindingsMixin(HTMLElement) {
|
|
54
|
+
@Listen('cart:updated', { target: 'document' })
|
|
55
|
+
onCartUpdated(event: DocumentEventMap['cart:updated']): void {
|
|
56
|
+
this.textContent = String(event.detail.itemCount);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Do not use `@Listen` without `EventBindingsMixin`; the decorated method deliberately throws when invoked on an incompatible class.
|
|
62
|
+
|
|
63
|
+
## Add element-aware logging
|
|
64
|
+
|
|
65
|
+
`LoggingMixin` prefixes output with the tag and element instance. `debug()` only prints when the element has a truthy `debug` attribute; `log()`, `warn()`, and `error()` remain visible. Values `false`, `0`, `off`, and `no` disable debugging.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { LoggingMixin } from '@trunkjs/browser-utils';
|
|
69
|
+
|
|
70
|
+
class DataPanel extends LoggingMixin(HTMLElement) {
|
|
71
|
+
connectedCallback(): void {
|
|
72
|
+
this.debug('connected', { source: this.dataset.source });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
attributeChangedCallback(name: string): void {
|
|
76
|
+
if (name === 'debug') this.invalidateDebugCache();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
loadFailed(error: unknown): void {
|
|
80
|
+
this.error('load failed', error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```html
|
|
86
|
+
<data-panel debug data-source="orders"></data-panel>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Use `throwError(...)` only when the caller should receive an exception. `getLogger(instanceId)` is cached per element, so choose a custom instance ID on the first call if one is required.
|
|
90
|
+
|
|
91
|
+
## Map a custom element to responsive modes
|
|
92
|
+
|
|
93
|
+
`BreakPointMixin` reads `--breakpoint` from the host and sets `mode="mobile|tablet|desktop"` as the window crosses the configured thresholds.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { BreakPointMixin } from '@trunkjs/browser-utils';
|
|
97
|
+
|
|
98
|
+
class AdaptiveNav extends BreakPointMixin(HTMLElement) {}
|
|
99
|
+
customElements.define('adaptive-nav', AdaptiveNav);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```css
|
|
103
|
+
adaptive-nav {
|
|
104
|
+
/* mobile below md, tablet from md, desktop from lg */
|
|
105
|
+
--breakpoint: 'md,lg';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
adaptive-nav[mode='mobile'] .labels {
|
|
109
|
+
display: none;
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
A single value uses the same threshold for tablet and desktop. Use only breakpoint names exported by the package.
|
|
114
|
+
|
|
115
|
+
## Coordinate Lit elements with the loader
|
|
116
|
+
|
|
117
|
+
`LoaderMixin` reports a Lit element as waiting during `connectedCallback`, ready after `firstUpdated`, and no longer blocking after disconnection. Pair it with the loader-aware wait functions where code must align with the visual startup phases.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { LoaderMixin, waitForPreVisual, waitForReady, waitForVisual } from '@trunkjs/browser-utils';
|
|
121
|
+
import { LitElement, css, html } from 'lit';
|
|
122
|
+
|
|
123
|
+
class HeroCard extends LoaderMixin(LitElement) {
|
|
124
|
+
static styles = css`:host { opacity: 0; } :host([visible]) { opacity: 1; }`;
|
|
125
|
+
|
|
126
|
+
render() {
|
|
127
|
+
return html`<slot></slot>`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
override async firstUpdated(changed: Map<string, unknown>) {
|
|
131
|
+
super.firstUpdated(changed);
|
|
132
|
+
await waitForPreVisual();
|
|
133
|
+
this.toggleAttribute('visible', true);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await waitForReady();
|
|
138
|
+
prepareFinalLayout();
|
|
139
|
+
await waitForVisual();
|
|
140
|
+
startNonEssentialAnimation();
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
When no TrunkJS loader sets `window.tj_loader_state`, these waits fall back to the normal window load event.
|
|
144
|
+
|
|
145
|
+
## Style empty slots in Lit
|
|
146
|
+
|
|
147
|
+
`SlotVisibilityMixin` adds `slot-empty` directly to an empty `<slot>` and updates it on `slotchange`. Whitespace and Lit comment markers do not count as content; fallback children do.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { SlotVisibilityMixin } from '@trunkjs/browser-utils';
|
|
151
|
+
import { LitElement, css, html } from 'lit';
|
|
152
|
+
|
|
153
|
+
class OptionalAside extends SlotVisibilityMixin(LitElement) {
|
|
154
|
+
static styles = css`
|
|
155
|
+
slot.slot-empty {
|
|
156
|
+
display: none;
|
|
157
|
+
}
|
|
158
|
+
`;
|
|
159
|
+
|
|
160
|
+
render() {
|
|
161
|
+
return html`<aside><slot name="aside"></slot></aside>`;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Compose mixins only as needed, keeping lifecycle-aware mixins in the inheritance chain:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
class InteractivePanel extends LoggingMixin(EventBindingsMixin(SlotVisibilityMixin(LitElement))) {
|
|
170
|
+
@Listen('visibilitychange', { target: 'document' })
|
|
171
|
+
onVisibilityChange(): void {
|
|
172
|
+
this.debug('document visibility', document.visibilityState);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Browser Helpers and Storage
|
|
2
|
+
|
|
3
|
+
Use these examples for ordinary browser code that does not need a custom-element mixin.
|
|
4
|
+
|
|
5
|
+
## Create DOM elements
|
|
6
|
+
|
|
7
|
+
`create_element` accepts string attributes, boolean attributes, and text or `Node` children. `true` creates an empty boolean attribute; `null` and `undefined` omit it.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { create_element } from '@trunkjs/browser-utils';
|
|
11
|
+
|
|
12
|
+
const icon = create_element('span', { class: 'icon', 'aria-hidden': 'true' }, '✓');
|
|
13
|
+
const button = create_element(
|
|
14
|
+
'button',
|
|
15
|
+
{ type: 'button', class: 'button primary', disabled: true },
|
|
16
|
+
[icon, ' Save'],
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
document.querySelector('#actions')?.append(button);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Use standard DOM APIs when a typed specialized element or property assignment is needed; `create_element` returns `HTMLElement` and sets attributes rather than element properties.
|
|
23
|
+
|
|
24
|
+
## Debounce repeated work
|
|
25
|
+
|
|
26
|
+
Use one `Debouncer` instance per independent operation. `max_delay` guarantees progress while calls keep arriving.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { Debouncer } from '@trunkjs/browser-utils';
|
|
30
|
+
|
|
31
|
+
const input = document.querySelector<HTMLInputElement>('#search')!;
|
|
32
|
+
const debouncer = new Debouncer(250, 1500);
|
|
33
|
+
|
|
34
|
+
input.addEventListener('input', async () => {
|
|
35
|
+
await debouncer.wait();
|
|
36
|
+
updateResults(input.value);
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For class methods using standard TypeScript decorators, use `@debounce`. A delayed invocation cannot return the original synchronous result, so use it for `void` methods.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { debounce } from '@trunkjs/browser-utils';
|
|
44
|
+
|
|
45
|
+
class SearchController {
|
|
46
|
+
@debounce(250, 1500)
|
|
47
|
+
update(query: string): void {
|
|
48
|
+
renderResults(query);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Wait for events and browser lifecycle
|
|
54
|
+
|
|
55
|
+
`waitFor` resolves with the first event and then removes its listener.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { waitFor } from '@trunkjs/browser-utils';
|
|
59
|
+
|
|
60
|
+
const dialog = document.querySelector<HTMLDialogElement>('dialog')!;
|
|
61
|
+
dialog.showModal();
|
|
62
|
+
|
|
63
|
+
const event = await waitFor<MouseEvent>(dialog, 'click', { capture: true });
|
|
64
|
+
console.log(event.clientX, event.clientY);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Wait for the DOM, full page, an image, media data, or a generic element load:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { waitForDomContentLoaded, waitForLoad } from '@trunkjs/browser-utils';
|
|
71
|
+
|
|
72
|
+
await waitForDomContentLoaded();
|
|
73
|
+
await waitForLoad();
|
|
74
|
+
|
|
75
|
+
const image = document.querySelector<HTMLImageElement>('img.hero')!;
|
|
76
|
+
try {
|
|
77
|
+
await waitForLoad(image);
|
|
78
|
+
} catch {
|
|
79
|
+
image.replaceWith(document.createTextNode('Preview unavailable'));
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Wait for CSS animation completion or introduce an explicit delay:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { sleep, waitForAnimationEnd } from '@trunkjs/browser-utils';
|
|
87
|
+
|
|
88
|
+
panel.classList.add('is-closing');
|
|
89
|
+
await waitForAnimationEnd(panel);
|
|
90
|
+
panel.hidden = true;
|
|
91
|
+
|
|
92
|
+
await sleep(200);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Persist JSON-like state
|
|
96
|
+
|
|
97
|
+
Storage helpers return a typed proxy. Reading initializes known keys from the supplied defaults; writing or deleting a property persists the whole object. Stored unknown keys are ignored when the proxy is created.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { local_storage, session_storage } from '@trunkjs/browser-utils';
|
|
101
|
+
|
|
102
|
+
const preferences = local_storage('dashboard.preferences', {
|
|
103
|
+
theme: 'system' as 'light' | 'dark' | 'system',
|
|
104
|
+
compact: false,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
preferences.theme = 'dark';
|
|
108
|
+
preferences.compact = true;
|
|
109
|
+
console.log({ ...preferences });
|
|
110
|
+
|
|
111
|
+
const draft = session_storage('contact.draft', { subject: '', message: '' });
|
|
112
|
+
draft.subject = 'Support request';
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Malformed stored JSON falls back to the initial value. Quota and storage-security errors are ignored, leaving the proxy usable in memory. Use a new versioned key or migrate explicitly when the data shape changes substantially.
|
|
116
|
+
|
|
117
|
+
## Inspect breakpoints
|
|
118
|
+
|
|
119
|
+
The package uses `xs`, `sm`, `md`, `lg`, `xl`, and `xxl` with Bootstrap-compatible minimum widths.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import {
|
|
123
|
+
breakpoints,
|
|
124
|
+
getBreakpointMinWidth,
|
|
125
|
+
getCurrentBreakpoint,
|
|
126
|
+
getViewportWidth,
|
|
127
|
+
} from '@trunkjs/browser-utils';
|
|
128
|
+
|
|
129
|
+
console.log(getCurrentBreakpoint());
|
|
130
|
+
console.log(getCurrentBreakpoint(1024)); // "lg"
|
|
131
|
+
console.log(getBreakpointMinWidth('xl')); // 1200
|
|
132
|
+
console.table(breakpoints);
|
|
133
|
+
console.log(getViewportWidth());
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Do not pass arbitrary names to `getBreakpointMinWidth`; unknown names throw.
|
|
137
|
+
|
|
138
|
+
## Diagnostics
|
|
139
|
+
|
|
140
|
+
Use `Stopwatch` for lightweight browser timing and `getErrorLocation` when presenting the best available file/line information from cross-browser errors.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { getErrorLocation, Stopwatch } from '@trunkjs/browser-utils';
|
|
144
|
+
|
|
145
|
+
const timing = new Stopwatch('hydrate', import.meta.env.DEV);
|
|
146
|
+
hydratePage();
|
|
147
|
+
timing.lap('DOM hydrated');
|
|
148
|
+
console.debug('Total milliseconds:', timing.stop());
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
runUserScript();
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (error instanceof Error) {
|
|
154
|
+
console.error('User script failed', getErrorLocation(error));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`Logger` is also public for non-element code. For custom elements, prefer `LoggingMixin` so messages include element identity and debug state.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: form-data-accessor-usage
|
|
3
|
+
description: Use @trunkjs/browser-utils FormDataAccessor to read or write named native and custom form controls, inspect element/value pairs, or create FormData from a DOM container.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# FormDataAccessor usage
|
|
7
|
+
|
|
8
|
+
Use `FormDataAccessor` for a small, framework-independent view of named value elements below a DOM root.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { FormDataAccessor } from '@trunkjs/browser-utils';
|
|
12
|
+
|
|
13
|
+
const accessor = new FormDataAccessor(container);
|
|
14
|
+
|
|
15
|
+
accessor.data = { name: 'Erika', topics: ['docs'] };
|
|
16
|
+
console.log(accessor.data);
|
|
17
|
+
console.log(accessor.formData);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The root may be an `HTMLElement`, `Document`, or `DocumentFragment`. Controls are discovered dynamically on every
|
|
21
|
+
access. A control needs a non-empty `name` and a readable/writable `value` property. Native inputs, textareas, selects,
|
|
22
|
+
and compatible custom elements such as `nte-input` follow this contract.
|
|
23
|
+
|
|
24
|
+
A named value element owns its complete value. Named descendants below it are not returned as additional entries. This
|
|
25
|
+
allows object-valued controls such as a named nested `tj-form` to form a data tree without duplicate flat fields.
|
|
26
|
+
|
|
27
|
+
## Data behavior
|
|
28
|
+
|
|
29
|
+
- `name[]` becomes an array under the name without `[]` in `data`.
|
|
30
|
+
- A radio group returns its selected value.
|
|
31
|
+
- One checkbox without `[]` returns a Boolean.
|
|
32
|
+
- Checkbox groups return selected option values.
|
|
33
|
+
- Multi-selects and custom controls may expose arrays.
|
|
34
|
+
- Assigning `data` is a partial update; names absent from the object are unchanged.
|
|
35
|
+
- `formData` omits disabled, unchecked, and non-submit controls.
|
|
36
|
+
|
|
37
|
+
## Element and value pairs
|
|
38
|
+
|
|
39
|
+
Use `entries` when code needs both the current value and its original element. Each entry has a dynamic `value`
|
|
40
|
+
getter/setter, so it can update the control without a wrapper API.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
for (const entry of accessor.entries) {
|
|
44
|
+
console.log(entry.name, entry.value, entry.element);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
accessor.entries.forEach(({ element }) => {
|
|
48
|
+
element.toggleAttribute('validated', true);
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Prefer direct iteration for operations such as disabling, validating, or marking controls invalid. Add specialized
|
|
53
|
+
collection methods only when a concrete repeated requirement cannot be expressed clearly through `entries`.
|