@pfern/elements 0.1.6 → 0.1.8
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/elements.js +62 -9
- package/package.json +1 -1
- package/types/elements.d.ts +9 -0
package/elements.js
CHANGED
|
@@ -24,7 +24,10 @@ const svgNS = 'http://www.w3.org/2000/svg'
|
|
|
24
24
|
*/
|
|
25
25
|
const rootMap = new WeakMap()
|
|
26
26
|
|
|
27
|
-
const isNodeEnv = typeof document === 'undefined'
|
|
27
|
+
const isNodeEnv = () => typeof document === 'undefined'
|
|
28
|
+
|
|
29
|
+
let componentUpdateDepth = 0
|
|
30
|
+
let currentEventRoot = null
|
|
28
31
|
|
|
29
32
|
/**
|
|
30
33
|
* Determines whether two nodes have changed enough to require replacement.
|
|
@@ -97,6 +100,8 @@ const assignProperties = (el, props) =>
|
|
|
97
100
|
while (target && !target.__root) target = target.parentNode
|
|
98
101
|
if (!target) return
|
|
99
102
|
|
|
103
|
+
const prevEventRoot = currentEventRoot
|
|
104
|
+
currentEventRoot = target
|
|
100
105
|
try {
|
|
101
106
|
const event = args[0]
|
|
102
107
|
const isFormEvent = /^(oninput|onsubmit|onchange)$/.test(key)
|
|
@@ -140,6 +145,8 @@ const assignProperties = (el, props) =>
|
|
|
140
145
|
}
|
|
141
146
|
} catch (error) {
|
|
142
147
|
console.error(error)
|
|
148
|
+
} finally {
|
|
149
|
+
currentEventRoot = prevEventRoot
|
|
143
150
|
}
|
|
144
151
|
}
|
|
145
152
|
} else if (key === 'style' && typeof value === 'object') {
|
|
@@ -172,7 +179,7 @@ const assignProperties = (el, props) =>
|
|
|
172
179
|
*/
|
|
173
180
|
const renderTree = (node, isRoot = true) => {
|
|
174
181
|
if (typeof node === 'string' || typeof node === 'number') {
|
|
175
|
-
return isNodeEnv ? node : document.createTextNode(node)
|
|
182
|
+
return isNodeEnv() ? node : document.createTextNode(node)
|
|
176
183
|
}
|
|
177
184
|
|
|
178
185
|
if (!node || node.length === 0) {
|
|
@@ -185,8 +192,12 @@ const renderTree = (node, isRoot = true) => {
|
|
|
185
192
|
}
|
|
186
193
|
|
|
187
194
|
if (Array.isArray(node) && node[0] === 'wrap') {
|
|
188
|
-
const [_tag,
|
|
189
|
-
|
|
195
|
+
const [_tag, props = {}, child] = node
|
|
196
|
+
const el = renderTree(child, true)
|
|
197
|
+
if (props && typeof props === 'object' && props.__instance) {
|
|
198
|
+
rootMap.set(props.__instance, el)
|
|
199
|
+
}
|
|
200
|
+
return el
|
|
190
201
|
}
|
|
191
202
|
|
|
192
203
|
const [tag, props = {}, ...children] = node
|
|
@@ -291,6 +302,34 @@ export const render = (vtree, container = null) => {
|
|
|
291
302
|
rootMap.set(vtree, target)
|
|
292
303
|
}
|
|
293
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Updates the browser URL via the History API (no full page load).
|
|
307
|
+
* No-ops outside the browser.
|
|
308
|
+
*
|
|
309
|
+
* @param {string} to - The target URL (path/search/hash).
|
|
310
|
+
* @param {Object} [options]
|
|
311
|
+
* @param {boolean} [options.replace=false] - Use replaceState instead of pushState.
|
|
312
|
+
* @param {boolean} [options.force=false] - Update even if URL is already `to`.
|
|
313
|
+
* @param {any} [options.state={}] - History state.
|
|
314
|
+
* @param {string} [options.title=''] - History title (mostly ignored by browsers).
|
|
315
|
+
*/
|
|
316
|
+
export const navigate = (to, {
|
|
317
|
+
replace = false,
|
|
318
|
+
force = false,
|
|
319
|
+
state = {},
|
|
320
|
+
title = ''
|
|
321
|
+
} = {}) => {
|
|
322
|
+
if (typeof window === 'undefined') return
|
|
323
|
+
if (typeof to !== 'string' || !to.length) return
|
|
324
|
+
|
|
325
|
+
const { pathname, search, hash } = window.location
|
|
326
|
+
const current = `${pathname}${search}${hash}`
|
|
327
|
+
if (!force && current === to) return
|
|
328
|
+
|
|
329
|
+
const method = replace ? 'replaceState' : 'pushState'
|
|
330
|
+
window.history[method](state, title, to)
|
|
331
|
+
}
|
|
332
|
+
|
|
294
333
|
/**
|
|
295
334
|
* Wraps a function component so that it participates in reconciliation.
|
|
296
335
|
*
|
|
@@ -298,16 +337,30 @@ export const render = (vtree, container = null) => {
|
|
|
298
337
|
* @returns {(...args: any[]) => any} - A callable component that can manage its own subtree.
|
|
299
338
|
*/
|
|
300
339
|
export const component = fn => {
|
|
340
|
+
const instance = {}
|
|
301
341
|
return (...args) => {
|
|
302
342
|
try {
|
|
303
|
-
const
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
343
|
+
const prevEl = rootMap.get(instance)
|
|
344
|
+
const canUpdateInPlace =
|
|
345
|
+
!!prevEl?.parentNode
|
|
346
|
+
&& componentUpdateDepth === 0
|
|
347
|
+
&& !currentEventRoot
|
|
348
|
+
|
|
349
|
+
componentUpdateDepth++
|
|
350
|
+
let vnode
|
|
351
|
+
try {
|
|
352
|
+
vnode = fn(...args)
|
|
353
|
+
} finally {
|
|
354
|
+
componentUpdateDepth--
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (canUpdateInPlace) {
|
|
358
|
+
const replacement = renderTree(['wrap', { __instance: instance }, vnode], true)
|
|
307
359
|
prevEl.parentNode.replaceChild(replacement, prevEl)
|
|
308
360
|
return replacement.__vnode
|
|
309
361
|
}
|
|
310
|
-
|
|
362
|
+
|
|
363
|
+
return ['wrap', { __instance: instance }, vnode]
|
|
311
364
|
} catch (err) {
|
|
312
365
|
console.error('Component error:', err)
|
|
313
366
|
return ['div', {}, `Error: ${err.message}`]
|
package/package.json
CHANGED
package/types/elements.d.ts
CHANGED
|
@@ -12,6 +12,15 @@
|
|
|
12
12
|
export const DEBUG: boolean;
|
|
13
13
|
export function render(vtree: any, container?: any): void;
|
|
14
14
|
export function component(fn: (...args: any[]) => any): (...args: any[]) => any;
|
|
15
|
+
export function navigate(
|
|
16
|
+
to: string,
|
|
17
|
+
options?: {
|
|
18
|
+
replace?: boolean;
|
|
19
|
+
force?: boolean;
|
|
20
|
+
state?: any;
|
|
21
|
+
title?: string;
|
|
22
|
+
}
|
|
23
|
+
): void;
|
|
15
24
|
/**
|
|
16
25
|
* @typedef {Record<string, any>} Props
|
|
17
26
|
* @typedef {any[] | string | number | boolean | null | undefined | Node} Child
|