@srfnstack/fntags 1.2.8 → 1.4.0
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/README.md +1 -1
- package/package.json +1 -1
- package/src/fnroute.d.mts +14 -2
- package/src/fnroute.d.mts.map +1 -1
- package/src/fnroute.mjs +79 -18
- package/src/fntags.d.mts +93 -6
- package/src/fntags.d.mts.map +1 -1
- package/src/fntags.mjs +2290 -550
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ fntags allows you to build complex, interactive web apps using standard JavaScri
|
|
|
13
13
|
## Why fntags?
|
|
14
14
|
|
|
15
15
|
- **No Build Step**: Import the framework directly from a CDN or your file system. No Webpack, no Babel, no headaches.
|
|
16
|
-
- **Granular State**: Bind only what needs to change
|
|
16
|
+
- **Granular State**: Bind only what needs to change (text, attributes, or styles) for high-performance updates.
|
|
17
17
|
- **Standards Based**: Just standard ES6 JavaScript and HTML5. Zero magic syntax to learn.
|
|
18
18
|
- **Effortless Debugging**: In fntags, there is no black box. Errors produce clean stack traces that point exactly to your source code.
|
|
19
19
|
- **TypeScript Support**: Includes TypeScript definitions out of the box. No need to install separate `@types` packages.
|
package/package.json
CHANGED
package/src/fnroute.d.mts
CHANGED
|
@@ -40,13 +40,25 @@ export function routeSwitch(...children: (any | Node)[]): Node | (() => Node);
|
|
|
40
40
|
*/
|
|
41
41
|
export function fnlink(...children: (any | Node)[]): HTMLAnchorElement;
|
|
42
42
|
/**
|
|
43
|
-
* A function to navigate to the specified route
|
|
43
|
+
* A function to navigate to the specified route.
|
|
44
|
+
*
|
|
45
|
+
* Navigating to the current path with only a different hash or query (for example from /docs to /docs#faq)
|
|
46
|
+
* and no new context is not a route change: the history entry is updated and the hash target is scrolled
|
|
47
|
+
* into view, but the route is not re-rendered and no route events are emitted.
|
|
44
48
|
* @param {string} route The route to navigate to
|
|
45
|
-
* @param {any} context Data related to the route change
|
|
49
|
+
* @param {any} [context] Data related to the route change
|
|
46
50
|
* @param {boolean} replace Whether to replace the state or push it. pushState is used by default.
|
|
47
51
|
* @param {boolean} silent Prevent route change events from being emitted for this route change
|
|
48
52
|
*/
|
|
49
53
|
export function goTo(route: string, context?: any, replace?: boolean, silent?: boolean): void;
|
|
54
|
+
/**
|
|
55
|
+
* Scroll the element whose id matches the hash into view. If the element does not exist yet, for example because
|
|
56
|
+
* the route's content is a promise that has not resolved, wait for it to be added to the document and scroll then.
|
|
57
|
+
* With no hash, scroll to the top of the page.
|
|
58
|
+
* @param {string|null|undefined} hash The hash to scroll to, with or without the leading #
|
|
59
|
+
* @param {number} [timeout] How long to wait for the element to appear, in milliseconds
|
|
60
|
+
*/
|
|
61
|
+
export function scrollToHash(hash: string | null | undefined, timeout?: number): void;
|
|
50
62
|
/**
|
|
51
63
|
* Listen for routing events
|
|
52
64
|
* @param {string} event a string event to listen for
|
package/src/fnroute.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fnroute.d.mts","sourceRoot":"","sources":["fnroute.mjs"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,mCAHW,CAAC,MAAO,IAAI,CAAC,EAAE,GACb,cAAc,CAoB1B;AAED;;;;;GAKG;AACH,yCAHW,CAAC,MAAO,IAAI,CAAC,EAAE,GACb,IAAI,GAAC,CAAC,MAAI,IAAI,CAAC,CAyB3B;AAoBD;;;;GAIG;AACH,oCAHW,CAAC,MAAO,IAAI,CAAC,EAAE,GACb,iBAAiB,CAuB7B;AAED
|
|
1
|
+
{"version":3,"file":"fnroute.d.mts","sourceRoot":"","sources":["fnroute.mjs"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,mCAHW,CAAC,MAAO,IAAI,CAAC,EAAE,GACb,cAAc,CAoB1B;AAED;;;;;GAKG;AACH,yCAHW,CAAC,MAAO,IAAI,CAAC,EAAE,GACb,IAAI,GAAC,CAAC,MAAI,IAAI,CAAC,CAyB3B;AAoBD;;;;GAIG;AACH,oCAHW,CAAC,MAAO,IAAI,CAAC,EAAE,GACb,iBAAiB,CAuB7B;AAED;;;;;;;;;;GAUG;AACH,4BALW,MAAM,YACN,GAAG,YACH,OAAO,WACP,OAAO,QA+CjB;AAID;;;;;;GAMG;AACH,mCAHW,MAAM,GAAC,IAAI,GAAC,SAAS,YACrB,MAAM,QAiChB;AAiED;;;;;;GAMG;AACH,iCALW,MAAM,iBAGL,MAAI,IAAI,CAanB;AAED;;;GAGG;AACH,sCAFW,MAAM,QAOhB;AAxFD;;;GAGG;AAEH;;;GAGG;AACH,6BAFU,OAAO,cAAc,EAAE,OAAO,CAAC,cAAc,CAAC,CAEf;AAEzC;;;GAGG;AAEH;;;GAGG;AACH,wBAFU,OAAO,cAAc,EAAE,OAAO,CAAC,SAAS,CAAC,CAO/C;AAMJ;;GAEG;AACH;;;GAGG;AACH,gCAFU,UAAU,CAEgC;AACpD;;;GAGG;AACH,+BAFU,UAAU,CAE8B;AAClD;;;GAGG;AACH,kCAFU,UAAU,CAEoC;;;;;;;;wBAnC3C;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAC;yBAmBrD,MAAM"}
|
package/src/fnroute.mjs
CHANGED
|
@@ -106,7 +106,7 @@ function updatePathParameters () {
|
|
|
106
106
|
* @returns {HTMLAnchorElement} An anchor element that will navigate to the specified route when clicked
|
|
107
107
|
*/
|
|
108
108
|
export function fnlink (...children) {
|
|
109
|
-
let context
|
|
109
|
+
let context
|
|
110
110
|
if (children[0] && children[0].context) {
|
|
111
111
|
context = children[0].context
|
|
112
112
|
}
|
|
@@ -129,21 +129,36 @@ export function fnlink (...children) {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
|
-
* A function to navigate to the specified route
|
|
132
|
+
* A function to navigate to the specified route.
|
|
133
|
+
*
|
|
134
|
+
* Navigating to the current path with only a different hash or query (for example from /docs to /docs#faq)
|
|
135
|
+
* and no new context is not a route change: the history entry is updated and the hash target is scrolled
|
|
136
|
+
* into view, but the route is not re-rendered and no route events are emitted.
|
|
133
137
|
* @param {string} route The route to navigate to
|
|
134
|
-
* @param {any} context Data related to the route change
|
|
138
|
+
* @param {any} [context] Data related to the route change
|
|
135
139
|
* @param {boolean} replace Whether to replace the state or push it. pushState is used by default.
|
|
136
140
|
* @param {boolean} silent Prevent route change events from being emitted for this route change
|
|
137
141
|
*/
|
|
138
|
-
export function goTo (route, context
|
|
142
|
+
export function goTo (route, context, replace = false, silent = false) {
|
|
139
143
|
const newPath = window.location.origin + makePath(route)
|
|
144
|
+
const hash = newPath.indexOf('#') > -1 ? newPath.split('#')[1] : null
|
|
145
|
+
const oldPathState = pathState()
|
|
146
|
+
const currentPath = ensureOnlyLeadingSlash(route.split(/[#?]/)[0])
|
|
140
147
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
148
|
+
if (currentPath === oldPathState.currentPath && context === undefined) {
|
|
149
|
+
if (replace) {
|
|
150
|
+
history.replaceState({}, route, newPath)
|
|
151
|
+
} else {
|
|
152
|
+
history.pushState({}, route, newPath)
|
|
153
|
+
}
|
|
154
|
+
scrollToHash(hash)
|
|
155
|
+
return
|
|
144
156
|
}
|
|
145
157
|
|
|
146
|
-
const
|
|
158
|
+
const patch = {
|
|
159
|
+
currentPath,
|
|
160
|
+
context: context === undefined ? {} : context
|
|
161
|
+
}
|
|
147
162
|
const newPathState = Object.assign({}, oldPathState, patch)
|
|
148
163
|
if (!silent) {
|
|
149
164
|
try {
|
|
@@ -160,25 +175,59 @@ export function goTo (route, context = {}, replace = false, silent = false) {
|
|
|
160
175
|
}
|
|
161
176
|
|
|
162
177
|
setTimeout(() => {
|
|
163
|
-
pathState.assign(
|
|
164
|
-
currentPath: route.split(/[#?]/)[0],
|
|
165
|
-
context
|
|
166
|
-
})
|
|
178
|
+
pathState.assign(patch)
|
|
167
179
|
if (!silent) {
|
|
168
180
|
emit(afterRouteChange, newPathState, oldPathState)
|
|
169
181
|
}
|
|
170
|
-
|
|
171
|
-
const el = document.getElementById(decodeURIComponent(newPath.split('#')[1]))
|
|
172
|
-
el && el.scrollIntoView()
|
|
173
|
-
} else {
|
|
174
|
-
window.scrollTo(0, 0)
|
|
175
|
-
}
|
|
182
|
+
scrollToHash(hash)
|
|
176
183
|
if (!silent) {
|
|
177
184
|
emit(routeChangeComplete, newPathState, oldPathState)
|
|
178
185
|
}
|
|
179
186
|
})
|
|
180
187
|
}
|
|
181
188
|
|
|
189
|
+
let cancelPendingScroll = null
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Scroll the element whose id matches the hash into view. If the element does not exist yet, for example because
|
|
193
|
+
* the route's content is a promise that has not resolved, wait for it to be added to the document and scroll then.
|
|
194
|
+
* With no hash, scroll to the top of the page.
|
|
195
|
+
* @param {string|null|undefined} hash The hash to scroll to, with or without the leading #
|
|
196
|
+
* @param {number} [timeout] How long to wait for the element to appear, in milliseconds
|
|
197
|
+
*/
|
|
198
|
+
export function scrollToHash (hash, timeout = 5000) {
|
|
199
|
+
if (cancelPendingScroll) {
|
|
200
|
+
cancelPendingScroll()
|
|
201
|
+
}
|
|
202
|
+
const id = hash ? decodeURIComponent(hash.startsWith('#') ? hash.slice(1) : hash) : ''
|
|
203
|
+
if (!id) {
|
|
204
|
+
window.scrollTo(0, 0)
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
const scrollIfPresent = () => {
|
|
208
|
+
const el = document.getElementById(id)
|
|
209
|
+
if (el) {
|
|
210
|
+
el.scrollIntoView()
|
|
211
|
+
}
|
|
212
|
+
return !!el
|
|
213
|
+
}
|
|
214
|
+
if (scrollIfPresent() || typeof MutationObserver === 'undefined') {
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
const observer = new MutationObserver(() => {
|
|
218
|
+
if (scrollIfPresent()) {
|
|
219
|
+
cancelPendingScroll()
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
const timer = setTimeout(() => cancelPendingScroll(), timeout)
|
|
223
|
+
cancelPendingScroll = () => {
|
|
224
|
+
observer.disconnect()
|
|
225
|
+
clearTimeout(timer)
|
|
226
|
+
cancelPendingScroll = null
|
|
227
|
+
}
|
|
228
|
+
observer.observe(document.documentElement, { childList: true, subtree: true })
|
|
229
|
+
}
|
|
230
|
+
|
|
182
231
|
const ensureOnlyLeadingSlash = (part) => removeTrailingSlash(part.startsWith('/') ? part : '/' + part)
|
|
183
232
|
|
|
184
233
|
const removeTrailingSlash = part => part.endsWith('/') && part.length > 1 ? part.slice(0, -1) : part
|
|
@@ -280,6 +329,12 @@ window.addEventListener(
|
|
|
280
329
|
const patch = {
|
|
281
330
|
currentPath: ensureOnlyLeadingSlash(window.location.pathname.replace(new RegExp('^' + pathState().rootPath), '')) || '/'
|
|
282
331
|
}
|
|
332
|
+
// Clicking <a href="#section"> and moving back/forward between hash entries fire popstate without
|
|
333
|
+
// changing the path. That is not a route change, so leave the rendered route alone and let the
|
|
334
|
+
// browser handle scrolling to the fragment.
|
|
335
|
+
if (patch.currentPath === oldPathState.currentPath) {
|
|
336
|
+
return
|
|
337
|
+
}
|
|
283
338
|
const newPathState = Object.assign({}, oldPathState, patch)
|
|
284
339
|
try {
|
|
285
340
|
emit(beforeRouteChange, newPathState, oldPathState)
|
|
@@ -294,6 +349,12 @@ window.addEventListener(
|
|
|
294
349
|
}
|
|
295
350
|
)
|
|
296
351
|
|
|
352
|
+
// On initial load the browser only scrolls to the hash if the target already exists. When the target is inside
|
|
353
|
+
// a route whose content has not rendered yet, wait for it the same way goTo does.
|
|
354
|
+
if (window.location.hash && !document.getElementById(decodeURIComponent(window.location.hash.slice(1)))) {
|
|
355
|
+
scrollToHash(window.location.hash)
|
|
356
|
+
}
|
|
357
|
+
|
|
297
358
|
const makePath = path => (pathState().rootPath === '/' ? '' : pathState().rootPath) + ensureOnlyLeadingSlash(path)
|
|
298
359
|
|
|
299
360
|
const makePathPattern = (path, absolute) => {
|
package/src/fntags.d.mts
CHANGED
|
@@ -13,10 +13,17 @@ export function setDevMode(on: boolean): void;
|
|
|
13
13
|
*
|
|
14
14
|
* Any attribute starting with 'on' that is a function is added as an event listener with the 'on' removed.
|
|
15
15
|
* i.e. { onclick: fn } gets added to the element as element.addEventListener('click', fn)
|
|
16
|
+
* An event attribute can also be an array [fn, arg], which calls fn.call(element, arg, event).
|
|
17
|
+
* For event types passed to delegateEvents, the handler is stored on the element and run by a single
|
|
18
|
+
* listener on document instead (see delegateEvents for how ordering differs).
|
|
16
19
|
*
|
|
17
20
|
* The style attribute can be an object and the properties of the object will be added as style properties to the element.
|
|
18
21
|
* i.e. { style: { color: blue } } becomes element.style.color = blue
|
|
19
22
|
*
|
|
23
|
+
* An attribute value of null, undefined or '' means no attribute: it is removed if present and never written,
|
|
24
|
+
* so { class: '' } produces an element without a class attribute. The value attribute and boolean attributes
|
|
25
|
+
* (disabled, checked, ...) are exempt and keep their property semantics.
|
|
26
|
+
*
|
|
20
27
|
* The rest of the arguments will be considered children of this element and appended to it in the same order as passed.
|
|
21
28
|
*
|
|
22
29
|
* @template {HTMLElement|SVGElement} T
|
|
@@ -31,10 +38,16 @@ export function h<T extends HTMLElement | SVGElement>(tag: string, ...children:
|
|
|
31
38
|
* @typedef FnStateObj A container for a state value that can be bound to.
|
|
32
39
|
* @property {(element?: (newValue: T, oldValue: T)=>(Node|any))=>Node} bindAs Bind this state to the given element function. This causes the element to be replaced when the state changes.
|
|
33
40
|
* If called with no parameters, the state's value will be rendered as an element.
|
|
34
|
-
* Do not wrap form inputs (input, textarea, select) in bindAs
|
|
41
|
+
* Do not wrap form inputs (input, textarea, select) in bindAs: it replaces the element on every update and the focused input will lose focus mid-keystroke. Bind the `value` attribute with bindAttr instead.
|
|
35
42
|
* @property {(parent: (()=>(Node|any))|any|Node, element: (childState: FnState)=>(Node|any))=>Node} bindChildren Bind the values of this state to the given element.
|
|
36
43
|
* Values are items/elements of an array.
|
|
37
44
|
* If the current value is not an array, this will behave the same as bindAs.
|
|
45
|
+
* @property {(parent: (()=>(Node|any))|Node, template: (context: any)=>Node, options?: BindRowsOptions)=>Node} bindRows Bind the values of this state
|
|
46
|
+
* to rows stamped from an fntemplate whose template has a single root element. `values(item, selected)` maps an item to the template's slots and
|
|
47
|
+
* runs at mount, then only when the item's reference changes or the row gains or loses selection; only changed slots are written. Slots named in
|
|
48
|
+
* `handlers` are event slots, called as handler(event, item) with the row's current item through one shared function per name.
|
|
49
|
+
* Rows keep no per-row state or subscriptions, so use this for rows that are a pure function of item and selection, and bindChildren for rows
|
|
50
|
+
* with their own state or nested reactivity. Items are compared by reference: update them immutably.
|
|
38
51
|
* @property {(prop: string)=>Node} bindProp Bind to a property of an object stored in this state instead of the state itself.
|
|
39
52
|
* Shortcut for `mystate.bindAs((current)=> current[prop])`
|
|
40
53
|
* @property {(attribute?: (newValue: T, oldValue: T)=>(string|any))=>any} bindAttr Bind attribute values to state changes.
|
|
@@ -55,6 +68,13 @@ export function h<T extends HTMLElement | SVGElement>(tag: string, ...children:
|
|
|
55
68
|
* @property {(subscriber: (newState: T, oldState: T)=>void) => void} subscribe Register a callback that will be executed whenever the state is changed
|
|
56
69
|
* @property {boolean} isFnState A flag to indicate that this is a fnstate object
|
|
57
70
|
*/
|
|
71
|
+
/**
|
|
72
|
+
* @typedef BindRowsOptions Options for bindRows.
|
|
73
|
+
* @property {(item: any, selected: any)=>Object<string, any>} [values] Map an item and the currently selected key to the template's slot values.
|
|
74
|
+
* Defaults to the item itself. Return a fresh object: it is kept and compared slot by slot against the next result.
|
|
75
|
+
* A slot value may also be a bind getter (bindAs, bindAttr, bindStyle) or a node; bindings created here are cleaned up when values re-runs or the row is removed.
|
|
76
|
+
* @property {Object<string, (event: Event, item: any)=>any>} [handlers] Event handlers by slot name. The named slots must be on* attributes.
|
|
77
|
+
*/
|
|
58
78
|
/**
|
|
59
79
|
* @template T The type of data stored in the state container
|
|
60
80
|
* @typedef {FnStateObj<T> & ((newState?: T)=>T)} FnState A container for a state value that can be bound to.
|
|
@@ -72,11 +92,40 @@ export function h<T extends HTMLElement | SVGElement>(tag: string, ...children:
|
|
|
72
92
|
*/
|
|
73
93
|
export function fnstate<T>(initialValue: T, mapKey?: (T: any) => any): FnState<T>;
|
|
74
94
|
/**
|
|
75
|
-
* Convert non objects (objects are assumed to be nodes) to text nodes and allow promises to resolve to nodes
|
|
95
|
+
* Convert non objects (objects are assumed to be nodes) to text nodes and allow promises to resolve to nodes.
|
|
96
|
+
* Arrays are flattened at any depth into a DocumentFragment.
|
|
76
97
|
* @param {any} node The node to render
|
|
77
98
|
* @returns {Node} The rendered node
|
|
78
99
|
*/
|
|
79
100
|
export function renderNode(node: any): Node;
|
|
101
|
+
/**
|
|
102
|
+
* Opt in to event delegation for the given event types. After this call, an `on<type>` handler on an element
|
|
103
|
+
* created by h(), or by an fntemplate compiled after this call, is stored on the element as a property
|
|
104
|
+
* (`element.__fnon_<type>`) instead of being added with addEventListener, and one listener per type is added to
|
|
105
|
+
* `document`. This saves a listener, and usually a closure, per element, which adds up in long lists.
|
|
106
|
+
*
|
|
107
|
+
* Semantics and caveats:
|
|
108
|
+
* - Only elements created after the call are delegated. Elements that already exist keep their listeners, and
|
|
109
|
+
* templates compiled before the call keep using addEventListener for every instance.
|
|
110
|
+
* - Delegated handlers run when the event bubbles up to `document`, after every native listener on the target
|
|
111
|
+
* and its ancestors has run. Calling stopPropagation() in a native listener therefore prevents delegated
|
|
112
|
+
* handlers from running, and a delegated handler cannot stop a native listener on an ancestor.
|
|
113
|
+
* - Among delegated handlers the order is innermost first, like native bubbling: the walk follows the event's
|
|
114
|
+
* composed path from the target up to `document`. stopPropagation() (or stopImmediatePropagation()) in a
|
|
115
|
+
* delegated handler stops the walk. Removing an element during dispatch does not shorten the walk, because
|
|
116
|
+
* the path is fixed when the event is dispatched, as it is for native listeners.
|
|
117
|
+
* - Inside a delegated handler `this` and `e.currentTarget` are the element the handler belongs to.
|
|
118
|
+
* - Elements must be connected to the document (directly or through open shadow roots) for handlers to run.
|
|
119
|
+
* - Events that do not bubble (focus, blur, mouseenter, mouseleave, load, scroll, ...) are never delegated:
|
|
120
|
+
* they are skipped with a dev-mode warning and keep using addEventListener.
|
|
121
|
+
* - Delegation cannot be turned off again for a type.
|
|
122
|
+
*
|
|
123
|
+
* Any `on<type>` attribute, delegated or not, also accepts `[fn, arg]`, which calls `fn.call(element, arg, event)`,
|
|
124
|
+
* so every row of a list can share one handler function instead of allocating a closure per row.
|
|
125
|
+
*
|
|
126
|
+
* @param {string|string[]} types the event types to delegate, e.g. ['click', 'input']
|
|
127
|
+
*/
|
|
128
|
+
export function delegateEvents(types: string | string[]): void;
|
|
80
129
|
/**
|
|
81
130
|
* Check if the given value is an object that can be used as attributes
|
|
82
131
|
* @param {any} val The value to check
|
|
@@ -136,9 +185,19 @@ export function hmrRoot(container: HTMLElement, appFn: (() => Node) | Node): HTM
|
|
|
136
185
|
* Create a compiled template function. The returned function takes a single object that contains the properties
|
|
137
186
|
* defined in the template.
|
|
138
187
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
188
|
+
* The template function receives a context function. Calling `ctx('prop')` returns a placeholder that can be used
|
|
189
|
+
* as a child, as an attribute value, or as a style value. The template is rendered once to a dom tree; each
|
|
190
|
+
* instantiation clones that tree and fills the placeholders from the context object, which avoids re-executing
|
|
191
|
+
* the tag functions and can speed up situations where a similar element is created many times.
|
|
192
|
+
*
|
|
193
|
+
* Child placeholders are empty text nodes in the template. A primitive context value is written into the text node
|
|
194
|
+
* in place. A bindAs/bindSelect getter whose current node is a text node adopts the placeholder as its live node,
|
|
195
|
+
* so state changes keep updating the clone. A function value is evaluated like renderNode does. Nodes, arrays and
|
|
196
|
+
* promises replace the placeholder with their rendered node. As with h(), an attribute value of '', null or undefined
|
|
197
|
+
* (including the initial value of a bound attribute) means no attribute.
|
|
198
|
+
*
|
|
199
|
+
* The locations of all placeholders are compiled once into a single firstChild/nextSibling walk over the clone,
|
|
200
|
+
* which stops at the last bound node, so instantiation does not depend on the size of the unbound parts of the tree.
|
|
142
201
|
*
|
|
143
202
|
* You cannot bind state to the initial template. If you attempt to, the state will be read, but the elements will
|
|
144
203
|
* not be updated when the state changes because they will not be bound to the cloned element.
|
|
@@ -156,7 +215,7 @@ export type FnStateObj<T> = {
|
|
|
156
215
|
/**
|
|
157
216
|
* Bind this state to the given element function. This causes the element to be replaced when the state changes.
|
|
158
217
|
* If called with no parameters, the state's value will be rendered as an element.
|
|
159
|
-
* Do not wrap form inputs (input, textarea, select) in bindAs
|
|
218
|
+
* Do not wrap form inputs (input, textarea, select) in bindAs: it replaces the element on every update and the focused input will lose focus mid-keystroke. Bind the `value` attribute with bindAttr instead.
|
|
160
219
|
*/
|
|
161
220
|
bindAs: (element?: (newValue: T, oldValue: T) => (Node | any)) => Node;
|
|
162
221
|
/**
|
|
@@ -165,6 +224,15 @@ export type FnStateObj<T> = {
|
|
|
165
224
|
* If the current value is not an array, this will behave the same as bindAs.
|
|
166
225
|
*/
|
|
167
226
|
bindChildren: (parent: (() => (Node | any)) | any | Node, element: (childState: any) => (Node | any)) => Node;
|
|
227
|
+
/**
|
|
228
|
+
* Bind the values of this state
|
|
229
|
+
* to rows stamped from an fntemplate whose template has a single root element. `values(item, selected)` maps an item to the template's slots and
|
|
230
|
+
* runs at mount, then only when the item's reference changes or the row gains or loses selection; only changed slots are written. Slots named in
|
|
231
|
+
* `handlers` are event slots, called as handler(event, item) with the row's current item through one shared function per name.
|
|
232
|
+
* Rows keep no per-row state or subscriptions, so use this for rows that are a pure function of item and selection, and bindChildren for rows
|
|
233
|
+
* with their own state or nested reactivity. Items are compared by reference: update them immutably.
|
|
234
|
+
*/
|
|
235
|
+
bindRows: (parent: (() => (Node | any)) | Node, template: (context: any) => Node, options?: BindRowsOptions) => Node;
|
|
168
236
|
/**
|
|
169
237
|
* Bind to a property of an object stored in this state instead of the state itself.
|
|
170
238
|
* Shortcut for `mystate.bindAs((current)=> current[prop])`
|
|
@@ -221,6 +289,25 @@ export type FnStateObj<T> = {
|
|
|
221
289
|
*/
|
|
222
290
|
isFnState: boolean;
|
|
223
291
|
};
|
|
292
|
+
/**
|
|
293
|
+
* Options for bindRows.
|
|
294
|
+
*/
|
|
295
|
+
export type BindRowsOptions = {
|
|
296
|
+
/**
|
|
297
|
+
* Map an item and the currently selected key to the template's slot values.
|
|
298
|
+
* Defaults to the item itself. Return a fresh object: it is kept and compared slot by slot against the next result.
|
|
299
|
+
* A slot value may also be a bind getter (bindAs, bindAttr, bindStyle) or a node; bindings created here are cleaned up when values re-runs or the row is removed.
|
|
300
|
+
*/
|
|
301
|
+
values?: (item: any, selected: any) => {
|
|
302
|
+
[x: string]: any;
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* Event handlers by slot name. The named slots must be on* attributes.
|
|
306
|
+
*/
|
|
307
|
+
handlers?: {
|
|
308
|
+
[x: string]: (event: Event, item: any) => any;
|
|
309
|
+
};
|
|
310
|
+
};
|
|
224
311
|
/**
|
|
225
312
|
* A container for a state value that can be bound to.
|
|
226
313
|
*/
|
package/src/fntags.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fntags.d.mts","sourceRoot":"","sources":["fntags.mjs"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fntags.d.mts","sourceRoot":"","sources":["../../../../../../../../Users/r/fntags/.claude/worktrees/agent-a4e6d365c3389d8c1/src/fntags.mjs"],"names":[],"mappings":"AAiCA;;;;;GAKG;AACH,+BAFW,OAAO,QAIjB;AAoBD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,2DALW,MAAM,eACH,CAAC,IAAI,MAAO,CAAC,OAM1B;AAuGD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH;;;;;;GAMG;AAEH;;;GAGG;AAEH;;;;;;;;;;GAUG;AACH,iEAPgB,GAAG,cA0ClB;AA84CD;;;;;GAKG;AACH,iCAHW,GAAG,GACD,IAAI,CAwChB;AA+DD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,sCAFW,MAAM,GAAC,MAAM,EAAE,QAiBzB;AAoHD;;;;GAIG;AACH,6BAHW,GAAG,GACD,OAAO,CAInB;AAED;;;;GAIG;AACH,mCAHW,GAAG,GACF,MAAM,CAIjB;AAED;;;;;;;;;;GAUG;AACH,kEALW,MAAM,GAAC,MAAM,OACb,MAAM,YACN,MAAM,EAAE,GAAC,IAAI,EAAE,KAezB;AAOD;;;;;;;;GAQG;AACH,wCAJW,MAAM,0BAiBhB;AAED;;;;;;;;;GASG;AACH,oCALW,MAAM,gBACN,GAAG,eACE,GAAG,KAAK,MAAM,OAc7B;AAED;;;;;;;;GAQG;AACH,mCAJW,WAAW,SACX,CAAC,MAAI,IAAI,CAAC,GAAC,IAAI,GACb,WAAW,CAWvB;AAsHD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qDAJkB,IAAI,iBACH,IAAI,CA6HtB;;;;;;;;;;kCA9mEmC,CAAC,YAAY,CAAC,KAAG,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;;;2BAG/C,CAAC,MAAI,CAAC,IAAI,GAAC,GAAG,CAAC,CAAC,GAAC,GAAG,GAAC,IAAI,gCAAkC,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;;;;;;uBAG5E,CAAC,MAAI,CAAC,IAAI,GAAC,GAAG,CAAC,CAAC,GAAC,IAAI,sBAAsB,GAAG,KAAG,IAAI,YAAY,eAAe,KAAG,IAAI;;;;;qBAMzF,MAAM,KAAG,IAAI;;;;;sCAEI,CAAC,YAAY,CAAC,KAAG,CAAC,MAAM,GAAC,GAAG,CAAC,KAAG,GAAG;;;;mCAEvC,CAAC,YAAY,CAAC,KAAG,MAAM,KAAK,MAAM;;;;yCAC7B,GAAG,KAAG,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;+CACrB,GAAG,KAAG,CAAC,MAAM,GAAC,GAAG,CAAC,KAAG,GAAG;;;;;;kBAC7C,GAAG,KAAG,IAAI;;;;cAGhB,MAAK,GAAG;;;;;qBACC,CAAC,KAAG,IAAI;;;;;;oBAEV,MAAM,KAAG,GAAG;;;;oBAGZ,MAAM,SAAS,GAAG,mBAAmB,OAAO,KAAG,IAAI;;;;uCAClC,CAAC,YAAY,CAAC,KAAG,IAAI,KAAK,IAAI;;;;eACtD,OAAO;;;;;;;;;;;oBAKA,GAAG,YAAY,GAAG;YAAU,MAAM,GAAE,GAAG;;;;;;6BAGvB,KAAK,QAAQ,GAAG,KAAG,GAAG;;;;;;sDAKjB,CAAC,KAAG,CAAC"}
|