@pfern/elements 0.1.7 → 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 +45 -6
- package/package.json +1 -1
- package/types/elements.d.ts +9 -0
package/elements.js
CHANGED
|
@@ -24,9 +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
28
|
|
|
29
29
|
let componentUpdateDepth = 0
|
|
30
|
+
let currentEventRoot = null
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Determines whether two nodes have changed enough to require replacement.
|
|
@@ -99,6 +100,8 @@ const assignProperties = (el, props) =>
|
|
|
99
100
|
while (target && !target.__root) target = target.parentNode
|
|
100
101
|
if (!target) return
|
|
101
102
|
|
|
103
|
+
const prevEventRoot = currentEventRoot
|
|
104
|
+
currentEventRoot = target
|
|
102
105
|
try {
|
|
103
106
|
const event = args[0]
|
|
104
107
|
const isFormEvent = /^(oninput|onsubmit|onchange)$/.test(key)
|
|
@@ -142,6 +145,8 @@ const assignProperties = (el, props) =>
|
|
|
142
145
|
}
|
|
143
146
|
} catch (error) {
|
|
144
147
|
console.error(error)
|
|
148
|
+
} finally {
|
|
149
|
+
currentEventRoot = prevEventRoot
|
|
145
150
|
}
|
|
146
151
|
}
|
|
147
152
|
} else if (key === 'style' && typeof value === 'object') {
|
|
@@ -174,7 +179,7 @@ const assignProperties = (el, props) =>
|
|
|
174
179
|
*/
|
|
175
180
|
const renderTree = (node, isRoot = true) => {
|
|
176
181
|
if (typeof node === 'string' || typeof node === 'number') {
|
|
177
|
-
return isNodeEnv ? node : document.createTextNode(node)
|
|
182
|
+
return isNodeEnv() ? node : document.createTextNode(node)
|
|
178
183
|
}
|
|
179
184
|
|
|
180
185
|
if (!node || node.length === 0) {
|
|
@@ -297,6 +302,34 @@ export const render = (vtree, container = null) => {
|
|
|
297
302
|
rootMap.set(vtree, target)
|
|
298
303
|
}
|
|
299
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
|
+
|
|
300
333
|
/**
|
|
301
334
|
* Wraps a function component so that it participates in reconciliation.
|
|
302
335
|
*
|
|
@@ -308,11 +341,18 @@ export const component = fn => {
|
|
|
308
341
|
return (...args) => {
|
|
309
342
|
try {
|
|
310
343
|
const prevEl = rootMap.get(instance)
|
|
311
|
-
const canUpdateInPlace =
|
|
344
|
+
const canUpdateInPlace =
|
|
345
|
+
!!prevEl?.parentNode
|
|
346
|
+
&& componentUpdateDepth === 0
|
|
347
|
+
&& !currentEventRoot
|
|
312
348
|
|
|
313
349
|
componentUpdateDepth++
|
|
314
|
-
|
|
315
|
-
|
|
350
|
+
let vnode
|
|
351
|
+
try {
|
|
352
|
+
vnode = fn(...args)
|
|
353
|
+
} finally {
|
|
354
|
+
componentUpdateDepth--
|
|
355
|
+
}
|
|
316
356
|
|
|
317
357
|
if (canUpdateInPlace) {
|
|
318
358
|
const replacement = renderTree(['wrap', { __instance: instance }, vnode], true)
|
|
@@ -322,7 +362,6 @@ export const component = fn => {
|
|
|
322
362
|
|
|
323
363
|
return ['wrap', { __instance: instance }, vnode]
|
|
324
364
|
} catch (err) {
|
|
325
|
-
componentUpdateDepth = Math.max(0, componentUpdateDepth - 1)
|
|
326
365
|
console.error('Component error:', err)
|
|
327
366
|
return ['div', {}, `Error: ${err.message}`]
|
|
328
367
|
}
|
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
|