@webjsdev/core 0.7.65 → 0.9.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 +5 -0
- package/dist/webjs-core-browser.js +6 -5
- package/dist/webjs-core-browser.js.map +3 -3
- package/dist/webjs-core-testing.js +22 -21
- package/dist/webjs-core-testing.js.map +3 -3
- package/dist/webjs-core.js +7 -6
- package/dist/webjs-core.js.map +3 -3
- package/index-browser.js +18 -9
- package/index.d.ts +26 -10
- package/index.js +21 -9
- package/package.json +15 -2
- package/src/action-effects-client.d.ts +5 -0
- package/src/action-effects-client.js +84 -0
- package/src/action-seed-client.js +5 -5
- package/src/asset-url.js +12 -13
- package/src/async-directive.d.ts +18 -0
- package/src/async-directive.js +88 -0
- package/src/attribute-directives.d.ts +6 -0
- package/src/attribute-directives.js +49 -0
- package/src/attribute-reader.js +213 -25
- package/src/binding-prefixes.js +10 -53
- package/src/component/lifecycle.js +921 -252
- package/src/component/reactive.js +7 -14
- package/src/component/server-element.js +23 -1
- package/src/component.d.ts +160 -44
- package/src/context.d.ts +94 -18
- package/src/context.js +341 -217
- package/src/converters.d.ts +7 -0
- package/src/converters.js +38 -0
- package/src/css.d.ts +32 -6
- package/src/css.js +178 -14
- package/src/directive.d.ts +58 -0
- package/src/directive.js +119 -0
- package/src/directives.d.ts +40 -1
- package/src/directives.js +160 -1
- package/src/form-action.d.ts +13 -0
- package/src/form-action.js +139 -4
- package/src/hot.js +23 -0
- package/src/html.d.ts +8 -0
- package/src/html.js +55 -2
- package/src/is-server.d.ts +2 -0
- package/src/is-server.js +18 -0
- package/src/jsx-guard.js +130 -0
- package/src/metadata.d.ts +22 -3
- package/src/nav.d.ts +4 -1
- package/src/nav.js +44 -11
- package/src/optimistic.js +2 -2
- package/src/refresh-header.js +52 -0
- package/src/registry.js +10 -1
- package/src/render-client/adopt-slots.js +250 -0
- package/src/render-client/parts.js +790 -42
- package/src/render-client/reconciler.js +84 -23
- package/src/render-client/template-compiler.js +152 -43
- package/src/render-client.d.ts +6 -1
- package/src/render-server/attrs.js +2 -2
- package/src/render-server/compile.js +50 -2
- package/src/render-server/errors.js +37 -0
- package/src/render-server/expand.js +100 -20
- package/src/render-server/served-attrs.js +78 -0
- package/src/render-server/walk.js +177 -5
- package/src/render-server.d.ts +3 -0
- package/src/render-server.js +2 -0
- package/src/repeat.d.ts +7 -2
- package/src/repeat.js +20 -6
- package/src/router-client/anchors.js +3 -1
- package/src/router-client/boundaries.js +15 -1
- package/src/router-client/constants.js +30 -0
- package/src/router-client/dom-differ.js +17 -0
- package/src/router-client/fetch-apply.js +107 -5
- package/src/router-client/frames.js +6 -3
- package/src/router-client/head-merge.js +58 -10
- package/src/router-client/navigator.js +49 -9
- package/src/router-client/owned-styles.js +372 -0
- package/src/router-client/prefetch.js +6 -4
- package/src/router-client/root-attrs.js +43 -0
- package/src/router-client/stream.js +7 -0
- package/src/router-client/stylesheets.js +191 -0
- package/src/router-client/swap.js +53 -10
- package/src/routes.d.ts +75 -18
- package/src/sentinels.d.ts +2 -0
- package/src/sentinels.js +4 -0
- package/src/serialize.d.ts +2 -0
- package/src/serialize.js +35 -4
- package/src/signal.d.ts +7 -0
- package/src/signal.js +26 -6
- package/src/slot/polyfills.js +3 -3
- package/src/slot.js +4 -8
- package/src/static-html.d.ts +22 -0
- package/src/static-html.js +144 -0
- package/src/task.d.ts +63 -20
- package/src/task.js +326 -293
- package/src/webjs-config.d.ts +2 -9
package/index-browser.js
CHANGED
|
@@ -23,25 +23,27 @@
|
|
|
23
23
|
* Keep this list in sync with `./index.js` for everything else.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
export { html, isTemplate, MARKER } from './src/html.js';
|
|
27
|
-
export {
|
|
26
|
+
export { html, svg, mathml, isTemplate, MARKER } from './src/html.js';
|
|
27
|
+
export { isServer } from './src/is-server.js';
|
|
28
|
+
export { css, unsafeCSS, CSSResult, isCSS, adoptStyles, stylesToString } from './src/css.js';
|
|
28
29
|
export { WebComponent, prop } from './src/component.js';
|
|
30
|
+
export { defaultConverter, notEqual } from './src/converters.js';
|
|
29
31
|
export { lookup, lookupModuleUrl, isLazy, allTags, primeModuleUrl, tagOf } from './src/registry.js';
|
|
30
32
|
export { render } from './src/render-client.js';
|
|
31
33
|
export { escapeText, escapeAttr } from './src/escape.js';
|
|
32
|
-
export { notFound, redirect, forbidden, unauthorized, isNotFound, isRedirect, isForbidden, isUnauthorized } from './src/nav.js';
|
|
34
|
+
export { notFound, redirect, permanentRedirect, forbidden, unauthorized, isNotFound, isRedirect, isForbidden, isUnauthorized } from './src/nav.js';
|
|
33
35
|
export { cspNonce } from './src/csp-nonce.js';
|
|
34
36
|
export { asset } from './src/asset-url.js';
|
|
35
37
|
// Form actions (#1155). The field name is shared with the server dispatcher;
|
|
36
38
|
// `setFormActionResolver` is server-only wiring and is stripped here, because
|
|
37
39
|
// a browser stub carries its own identity and needs no resolver.
|
|
38
|
-
export { FORM_ACTION_FIELD, FORM_ACTION_ID_KEY } from './src/form-action.js';
|
|
40
|
+
export { FORM_ACTION_FIELD, FORM_ACTION_ID_KEY, installActionBind, bindActionId, splitActionId } from './src/form-action.js';
|
|
39
41
|
export { repeat, isRepeat } from './src/repeat.js';
|
|
40
42
|
export { Suspense, isSuspense } from './src/suspense.js';
|
|
41
43
|
export { connectWS } from './src/websocket-client.js';
|
|
42
44
|
export { richFetch } from './src/rich-fetch.js';
|
|
43
45
|
export {
|
|
44
|
-
stringify, parse,
|
|
46
|
+
stringify, stringifySync, parse,
|
|
45
47
|
serialize, deserialize,
|
|
46
48
|
} from './src/serialize.js';
|
|
47
49
|
// The client router auto-enables on bundle load: `router-client.js` calls
|
|
@@ -65,7 +67,7 @@ export { WebjsFrame } from './src/webjs-frame.js';
|
|
|
65
67
|
export { WebjsStream, renderStream } from './src/webjs-stream.js';
|
|
66
68
|
|
|
67
69
|
// Signals (TC39 Stage-1 shape), also available via '@webjsdev/core/signals'
|
|
68
|
-
export { signal, computed, effect, batch, isSignal, Signal } from './src/signal.js';
|
|
70
|
+
export { signal, computed, effect, batch, isSignal, Signal, SignalWatcher } from './src/signal.js';
|
|
69
71
|
|
|
70
72
|
// Optimistic-mutation helper (thin signal wrapper, rolls back on failure)
|
|
71
73
|
export { optimistic } from './src/optimistic.js';
|
|
@@ -76,6 +78,9 @@ export { takeSeed, scanSeeds, seedStats, SEED_MISS } from './src/action-seed-cli
|
|
|
76
78
|
// Client tag-cache coordinator for HTTP-verb actions (#488): tag-based
|
|
77
79
|
// browser-cache eviction after a mutation. Inert server-side.
|
|
78
80
|
export { markStale, registerKeyTags, consumeStale, parseTagHeader, fetchMark } from './src/action-cache-client.js';
|
|
81
|
+
// Effects an RPC action response asks the browser for (revalidatePath's
|
|
82
|
+
// in-place refresh), read by the generated action stub.
|
|
83
|
+
export { applyActionRefresh, followActionRedirect } from './src/action-effects-client.js';
|
|
79
84
|
// Client action-abort plumbing (#492): a superseded async render aborts its
|
|
80
85
|
// in-flight action fetches. Inert server-side.
|
|
81
86
|
export { setActiveActionSignal, activeActionSignal } from './src/action-abort-client.js';
|
|
@@ -92,11 +97,15 @@ export {
|
|
|
92
97
|
unsafeHTML, isUnsafeHTML, live, isLive, keyed, isKeyed, guard, isGuard,
|
|
93
98
|
templateContent, isTemplateContent, ref, isRef, createRef, cache, isCache,
|
|
94
99
|
until, isUntil, asyncAppend, isAsyncAppend, asyncReplace, isAsyncReplace,
|
|
95
|
-
watch, isWatch,
|
|
100
|
+
watch, isWatch, unsafeSVG, unsafeMathML, when, choose, map, join, range,
|
|
96
101
|
} from './src/directives.js';
|
|
97
102
|
|
|
98
103
|
// Context Protocol, also available via '@webjsdev/core/context'
|
|
99
|
-
export { createContext, ContextProvider, ContextConsumer, ContextRequestEvent } from './src/context.js';
|
|
104
|
+
export { createContext, ContextProvider, ContextConsumer, ContextRequestEvent, ContextProviderEvent, ContextRoot } from './src/context.js';
|
|
100
105
|
|
|
101
106
|
// Task controller, also available via '@webjsdev/core/task'
|
|
102
|
-
export { Task, TaskStatus } from './src/task.js';
|
|
107
|
+
export { Task, TaskStatus, initialState, shallowArrayEquals, deepArrayEquals } from './src/task.js';
|
|
108
|
+
|
|
109
|
+
export { nothing, noChange } from './src/sentinels.js';
|
|
110
|
+
|
|
111
|
+
export { classMap, styleMap, ifDefined } from './src/attribute-directives.js';
|
package/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
export * from './src/component.js';
|
|
12
|
+
export * from './src/converters.js';
|
|
12
13
|
export type {
|
|
13
14
|
Metadata,
|
|
14
15
|
MetadataContext,
|
|
@@ -25,6 +26,7 @@ export type {
|
|
|
25
26
|
AuthorMetadata,
|
|
26
27
|
PreloadDescriptor,
|
|
27
28
|
JsonLd,
|
|
29
|
+
GenerateMetadata,
|
|
28
30
|
} from './src/metadata.d.ts';
|
|
29
31
|
|
|
30
32
|
// Typed route props + the opt-in generated route union (#258). The
|
|
@@ -36,13 +38,14 @@ export type {
|
|
|
36
38
|
RouteParams,
|
|
37
39
|
PageProps,
|
|
38
40
|
LayoutProps,
|
|
41
|
+
FormActionData,
|
|
39
42
|
RouteHandlerContext,
|
|
40
43
|
WebjsRoutes,
|
|
41
44
|
RouteParamMap,
|
|
42
45
|
} from './src/routes.d.ts';
|
|
43
46
|
|
|
44
47
|
// The package.json `webjs` config block (#259). Typed reference for the
|
|
45
|
-
//
|
|
48
|
+
// seed / headers / redirects / trailingSlash / csp / ingress-limit knobs;
|
|
46
49
|
// the companion JSON Schema (packages/server/webjs-config.schema.json) gives
|
|
47
50
|
// editors native validation of package.json itself.
|
|
48
51
|
export type {
|
|
@@ -74,20 +77,25 @@ export type {
|
|
|
74
77
|
NonSerializable,
|
|
75
78
|
} from './src/serializable.d.ts';
|
|
76
79
|
|
|
77
|
-
export { html, isTemplate, MARKER } from './src/html.js';
|
|
80
|
+
export { html, svg, mathml, isTemplate, MARKER } from './src/html.js';
|
|
81
|
+
export { isServer } from './src/is-server.js';
|
|
78
82
|
// The `html\`\`` return type. A JSDoc typedef in html.js used across the
|
|
79
83
|
// component / route / testing type surfaces; re-exported so an app or the
|
|
80
84
|
// docs/website can annotate a value as `TemplateResult` (#772).
|
|
81
85
|
export type { TemplateResult } from './src/html.js';
|
|
82
|
-
export { css, isCSS, adoptStyles, stylesToString } from './src/css.js';
|
|
86
|
+
export { css, unsafeCSS, CSSResult, isCSS, adoptStyles, stylesToString } from './src/css.js';
|
|
87
|
+
export type { CSSResultGroup, CSSResultOrNative, CSSResultArray } from './src/css.js';
|
|
83
88
|
export { lookup, lookupModuleUrl, isLazy, allTags, primeModuleUrl, tagOf } from './src/registry.js';
|
|
84
89
|
export { renderToString, renderToStream } from './src/render-server.js';
|
|
85
90
|
export { render } from './src/render-client.js';
|
|
91
|
+
export type { RootPart } from './src/render-client.js';
|
|
86
92
|
export { escapeText, escapeAttr } from './src/escape.js';
|
|
87
|
-
export { notFound, redirect, forbidden, unauthorized, isNotFound, isRedirect, isForbidden, isUnauthorized } from './src/nav.js';
|
|
93
|
+
export { notFound, redirect, permanentRedirect, forbidden, unauthorized, isNotFound, isRedirect, isForbidden, isUnauthorized } from './src/nav.js';
|
|
94
|
+
export type { RedirectType } from './src/nav.js';
|
|
88
95
|
export { repeat, isRepeat } from './src/repeat.js';
|
|
89
96
|
export { Suspense, isSuspense } from './src/suspense.js';
|
|
90
97
|
export { connectWS } from './src/websocket-client.js';
|
|
98
|
+
export type { WSConnection, ConnectOptions } from './src/websocket-client.js';
|
|
91
99
|
export { richFetch } from './src/rich-fetch.js';
|
|
92
100
|
export { enableClientRouter, disableClientRouter, revalidate, refreshPage, loadFrame } from './src/router-client.js';
|
|
93
101
|
|
|
@@ -103,21 +111,25 @@ export {
|
|
|
103
111
|
unsafeHTML, isUnsafeHTML, live, isLive, keyed, isKeyed, guard, isGuard,
|
|
104
112
|
templateContent, isTemplateContent, ref, isRef, createRef, cache, isCache,
|
|
105
113
|
until, isUntil, asyncAppend, isAsyncAppend, asyncReplace, isAsyncReplace,
|
|
106
|
-
watch, isWatch,
|
|
114
|
+
watch, isWatch, unsafeSVG, unsafeMathML, when, choose, map, join, range,
|
|
107
115
|
} from './src/directives.js';
|
|
108
|
-
export { createContext, ContextProvider, ContextConsumer, ContextRequestEvent } from './src/context.js';
|
|
109
|
-
export { Task, TaskStatus } from './src/task.js';
|
|
116
|
+
export { createContext, ContextProvider, ContextConsumer, ContextRequestEvent, ContextProviderEvent, ContextRoot } from './src/context.js';
|
|
117
|
+
export { Task, TaskStatus, initialState, shallowArrayEquals, deepArrayEquals } from './src/task.js';
|
|
110
118
|
|
|
111
119
|
// Signals (the default state primitive, invariant 5), the CSP nonce reader,
|
|
112
120
|
// the wire serializer, and the streaming/frame custom elements. These mirror
|
|
113
121
|
// the index.js runtime re-exports; their declarations live in the matching
|
|
114
122
|
// src/*.d.ts (#388 fixed the index.d.ts drift from index.js).
|
|
115
|
-
export { signal, computed, effect, batch, isSignal, Signal } from './src/signal.js';
|
|
123
|
+
export { signal, computed, effect, batch, isSignal, Signal, SignalWatcher } from './src/signal.js';
|
|
116
124
|
export { cspNonce, setCspNonceProvider } from './src/csp-nonce.js';
|
|
117
125
|
export { asset, setAssetUrlProvider } from './src/asset-url.js';
|
|
118
|
-
export {
|
|
126
|
+
export {
|
|
127
|
+
FORM_ACTION_FIELD, FORM_ACTION_ID_KEY, setFormActionResolver,
|
|
128
|
+
installActionBind, bindActionId, splitActionId,
|
|
129
|
+
} from './src/form-action.js';
|
|
119
130
|
export { takeSeed, scanSeeds, seedStats, SEED_MISS } from './src/action-seed-client.js';
|
|
120
131
|
export { markStale, registerKeyTags, consumeStale, parseTagHeader, fetchMark } from './src/action-cache-client.js';
|
|
132
|
+
export { applyActionRefresh, followActionRedirect } from './src/action-effects-client.js';
|
|
121
133
|
// Client action-abort plumbing (#492): a superseded async render aborts its
|
|
122
134
|
// in-flight action fetches. Inert server-side.
|
|
123
135
|
export { setActiveActionSignal, activeActionSignal } from './src/action-abort-client.js';
|
|
@@ -125,7 +137,7 @@ export { setActiveActionSignal, activeActionSignal } from './src/action-abort-cl
|
|
|
125
137
|
export {
|
|
126
138
|
STREAM_CONTENT_TYPE, FRAME_CHUNK, FRAME_END, FRAME_ERROR, encodeFrame, createFrameDecoder,
|
|
127
139
|
} from './src/action-stream.js';
|
|
128
|
-
export { stringify, parse, serialize, deserialize } from './src/serialize.js';
|
|
140
|
+
export { stringify, stringifySync, parse, serialize, deserialize } from './src/serialize.js';
|
|
129
141
|
export { WebjsFrame } from './src/webjs-frame.js';
|
|
130
142
|
export { WebjsStream, renderStream } from './src/webjs-stream.js';
|
|
131
143
|
|
|
@@ -159,3 +171,7 @@ export function optimistic<T, R>(
|
|
|
159
171
|
action: () => Promise<R> | R,
|
|
160
172
|
): Promise<R>;
|
|
161
173
|
|
|
174
|
+
|
|
175
|
+
export { nothing, noChange } from './src/sentinels.js';
|
|
176
|
+
|
|
177
|
+
export { classMap, styleMap, ifDefined } from './src/attribute-directives.js';
|
package/index.js
CHANGED
|
@@ -6,26 +6,31 @@
|
|
|
6
6
|
* renderer is only reached on the server.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
export { html, isTemplate, MARKER } from './src/html.js';
|
|
10
|
-
export {
|
|
9
|
+
export { html, svg, mathml, isTemplate, MARKER } from './src/html.js';
|
|
10
|
+
export { isServer } from './src/is-server.js';
|
|
11
|
+
export { css, unsafeCSS, CSSResult, isCSS, adoptStyles, stylesToString } from './src/css.js';
|
|
11
12
|
export { WebComponent, prop } from './src/component.js';
|
|
13
|
+
export { defaultConverter, notEqual } from './src/converters.js';
|
|
12
14
|
export { lookup, lookupModuleUrl, isLazy, allTags, primeModuleUrl, tagOf } from './src/registry.js';
|
|
13
15
|
export { renderToString, renderToStream } from './src/render-server.js';
|
|
14
16
|
export { render } from './src/render-client.js';
|
|
15
17
|
export { escapeText, escapeAttr } from './src/escape.js';
|
|
16
|
-
export { notFound, redirect, forbidden, unauthorized, isNotFound, isRedirect, isForbidden, isUnauthorized } from './src/nav.js';
|
|
18
|
+
export { notFound, redirect, permanentRedirect, forbidden, unauthorized, isNotFound, isRedirect, isForbidden, isUnauthorized } from './src/nav.js';
|
|
17
19
|
export { cspNonce, setCspNonceProvider } from './src/csp-nonce.js';
|
|
18
20
|
export { asset, setAssetUrlProvider } from './src/asset-url.js';
|
|
19
21
|
// Form actions (#1155). `FORM_ACTION_FIELD` is the hidden field both renderers
|
|
20
22
|
// emit and the server's form dispatcher reads; `setFormActionResolver` is the
|
|
21
23
|
// server-only wiring that maps a real action function back to its identity.
|
|
22
|
-
export {
|
|
24
|
+
export {
|
|
25
|
+
FORM_ACTION_FIELD, FORM_ACTION_ID_KEY, setFormActionResolver,
|
|
26
|
+
installActionBind, bindActionId, splitActionId,
|
|
27
|
+
} from './src/form-action.js';
|
|
23
28
|
export { repeat, isRepeat } from './src/repeat.js';
|
|
24
29
|
export { Suspense, isSuspense } from './src/suspense.js';
|
|
25
30
|
export { connectWS } from './src/websocket-client.js';
|
|
26
31
|
export { richFetch } from './src/rich-fetch.js';
|
|
27
32
|
export {
|
|
28
|
-
stringify, parse,
|
|
33
|
+
stringify, stringifySync, parse,
|
|
29
34
|
serialize, deserialize,
|
|
30
35
|
} from './src/serialize.js';
|
|
31
36
|
export { enableClientRouter, disableClientRouter, navigate, revalidate, refreshPage, loadFrame } from './src/router-client.js';
|
|
@@ -33,7 +38,7 @@ export { WebjsFrame } from './src/webjs-frame.js';
|
|
|
33
38
|
export { WebjsStream, renderStream } from './src/webjs-stream.js';
|
|
34
39
|
|
|
35
40
|
// Signals (TC39 Stage-1 shape), also available via '@webjsdev/core/signals'
|
|
36
|
-
export { signal, computed, effect, batch, isSignal, Signal } from './src/signal.js';
|
|
41
|
+
export { signal, computed, effect, batch, isSignal, Signal, SignalWatcher } from './src/signal.js';
|
|
37
42
|
|
|
38
43
|
// Optimistic-mutation helper (thin signal wrapper, rolls back on failure)
|
|
39
44
|
export { optimistic } from './src/optimistic.js';
|
|
@@ -44,6 +49,9 @@ export { takeSeed, scanSeeds, seedStats, SEED_MISS } from './src/action-seed-cli
|
|
|
44
49
|
// Client tag-cache coordinator for HTTP-verb actions (#488): tag-based
|
|
45
50
|
// browser-cache eviction after a mutation. Inert server-side.
|
|
46
51
|
export { markStale, registerKeyTags, consumeStale, parseTagHeader, fetchMark } from './src/action-cache-client.js';
|
|
52
|
+
// Effects an RPC action response asks the browser for (revalidatePath's
|
|
53
|
+
// in-place refresh), read by the generated action stub.
|
|
54
|
+
export { applyActionRefresh, followActionRedirect } from './src/action-effects-client.js';
|
|
47
55
|
// Client action-abort plumbing (#492): a superseded async render aborts its
|
|
48
56
|
// in-flight action fetches. Inert server-side.
|
|
49
57
|
export { setActiveActionSignal, activeActionSignal } from './src/action-abort-client.js';
|
|
@@ -60,11 +68,15 @@ export {
|
|
|
60
68
|
unsafeHTML, isUnsafeHTML, live, isLive, keyed, isKeyed, guard, isGuard,
|
|
61
69
|
templateContent, isTemplateContent, ref, isRef, createRef, cache, isCache,
|
|
62
70
|
until, isUntil, asyncAppend, isAsyncAppend, asyncReplace, isAsyncReplace,
|
|
63
|
-
watch, isWatch,
|
|
71
|
+
watch, isWatch, unsafeSVG, unsafeMathML, when, choose, map, join, range,
|
|
64
72
|
} from './src/directives.js';
|
|
65
73
|
|
|
66
74
|
// Context Protocol, also available via '@webjsdev/core/context'
|
|
67
|
-
export { createContext, ContextProvider, ContextConsumer, ContextRequestEvent } from './src/context.js';
|
|
75
|
+
export { createContext, ContextProvider, ContextConsumer, ContextRequestEvent, ContextProviderEvent, ContextRoot } from './src/context.js';
|
|
68
76
|
|
|
69
77
|
// Task controller, also available via '@webjsdev/core/task'
|
|
70
|
-
export { Task, TaskStatus } from './src/task.js';
|
|
78
|
+
export { Task, TaskStatus, initialState, shallowArrayEquals, deepArrayEquals } from './src/task.js';
|
|
79
|
+
|
|
80
|
+
export { nothing, noChange } from './src/sentinels.js';
|
|
81
|
+
|
|
82
|
+
export { classMap, styleMap, ifDefined } from './src/attribute-directives.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webjsdev/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The runtime for WebJs, a full-stack JavaScript framework built on web components with server-side rendering and no build step. Ships the html and css template tags, the WebComponent base class, signals, directives, and the isomorphic renderers.",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -58,7 +58,20 @@
|
|
|
58
58
|
},
|
|
59
59
|
"./signals": {
|
|
60
60
|
"types": "./src/signal.d.ts",
|
|
61
|
-
"
|
|
61
|
+
"source": "./src/signal.js",
|
|
62
|
+
"default": "./dist/webjs-core-browser.js"
|
|
63
|
+
},
|
|
64
|
+
"./directive": {
|
|
65
|
+
"types": "./src/directive.d.ts",
|
|
66
|
+
"default": "./src/directive.js"
|
|
67
|
+
},
|
|
68
|
+
"./async-directive": {
|
|
69
|
+
"types": "./src/async-directive.d.ts",
|
|
70
|
+
"default": "./src/async-directive.js"
|
|
71
|
+
},
|
|
72
|
+
"./static-html": {
|
|
73
|
+
"types": "./src/static-html.d.ts",
|
|
74
|
+
"default": "./src/static-html.js"
|
|
62
75
|
},
|
|
63
76
|
"./package.json": "./package.json"
|
|
64
77
|
},
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Browser effects a server action's RPC response asks for (revalidatePath's in-place refresh). Read by the generated action stub. */
|
|
2
|
+
export function parseRefreshHeader(value: string | null | undefined): { path: string; layout: boolean }[];
|
|
3
|
+
export function refreshCovers(entry: { path: string; layout: boolean }, pathname: string): boolean;
|
|
4
|
+
export function applyActionRefresh(value: string | null | undefined): Promise<boolean>;
|
|
5
|
+
export function followActionRedirect(value: string): Promise<never>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side effects a server action's RPC response asks for, the Next.js
|
|
3
|
+
* behaviours an `await action()` call has after the action ran:
|
|
4
|
+
*
|
|
5
|
+
* - `x-webjs-refresh` (written when the action called `revalidatePath`):
|
|
6
|
+
* evict the client router's cached copies of those paths and, when the
|
|
7
|
+
* page on screen is one of them, re-render it in place (`refreshPage`), so
|
|
8
|
+
* the caller sees fresh server data with no reload.
|
|
9
|
+
* - `x-webjs-redirect` (the action threw `redirect()` / `permanentRedirect()`):
|
|
10
|
+
* navigate there with the client router (a full load for another origin),
|
|
11
|
+
* and reject the call with the redirect sentinel, so code after
|
|
12
|
+
* `await action()` does not run, exactly as in Next.
|
|
13
|
+
*
|
|
14
|
+
* The generated action stub calls these; app code does not need to.
|
|
15
|
+
*
|
|
16
|
+
* @module action-effects-client
|
|
17
|
+
*/
|
|
18
|
+
import { revalidate, refreshPage, navigate } from './router-client/navigator.js';
|
|
19
|
+
import { isRedirect } from './nav.js';
|
|
20
|
+
import { parseRefreshHeader, refreshCovers } from './refresh-header.js';
|
|
21
|
+
|
|
22
|
+
export { parseRefreshHeader, refreshCovers };
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Apply an `x-webjs-refresh` header: evict the cached snapshots it names, and
|
|
26
|
+
* refresh the current page in place when it is one of them (a `layout` entry
|
|
27
|
+
* refreshes the whole shell, since the layout's own markup may have changed).
|
|
28
|
+
* Resolves once the refresh applied, so `await action()` returns after the
|
|
29
|
+
* page shows the new data, as in Next.
|
|
30
|
+
* @param {string | null | undefined} value
|
|
31
|
+
* @returns {Promise<boolean>} whether the current page was refreshed
|
|
32
|
+
*/
|
|
33
|
+
export async function applyActionRefresh(value) {
|
|
34
|
+
const entries = parseRefreshHeader(value);
|
|
35
|
+
if (!entries.length || typeof location === 'undefined') return false;
|
|
36
|
+
let current = false;
|
|
37
|
+
let shell = false;
|
|
38
|
+
for (const e of entries) {
|
|
39
|
+
const pattern = e.layout || e.path.includes('[');
|
|
40
|
+
if (pattern) revalidate();
|
|
41
|
+
else revalidate(e.path);
|
|
42
|
+
if (refreshCovers(e, location.pathname)) {
|
|
43
|
+
current = true;
|
|
44
|
+
if (e.layout) shell = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!current) return false;
|
|
48
|
+
try { return await refreshPage(shell ? 'shell' : 'page'); } catch { return false; }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let rejectionFilterInstalled = false;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Follow an `x-webjs-redirect` header (`<url>;<push|replace>`): a same-origin
|
|
55
|
+
* target goes through the client router, another origin is a full load.
|
|
56
|
+
* Returns a promise rejected with the redirect sentinel, and makes sure an
|
|
57
|
+
* unhandled one (a fire-and-forget `action()` in a click handler) is not
|
|
58
|
+
* reported as an error, since it is navigation.
|
|
59
|
+
* @param {string} value
|
|
60
|
+
* @returns {Promise<never>}
|
|
61
|
+
*/
|
|
62
|
+
export function followActionRedirect(value) {
|
|
63
|
+
const cut = value.lastIndexOf(';');
|
|
64
|
+
const url = cut === -1 ? value : value.slice(0, cut);
|
|
65
|
+
const replace = cut !== -1 && value.slice(cut + 1) === 'replace';
|
|
66
|
+
const err = new Error(`webjs: redirect(${url})`);
|
|
67
|
+
/** @type {any} */ (err).__webjs = Symbol.for('webjs.redirect');
|
|
68
|
+
/** @type {any} */ (err).url = url;
|
|
69
|
+
if (typeof location !== 'undefined') {
|
|
70
|
+
if (!rejectionFilterInstalled && typeof addEventListener === 'function') {
|
|
71
|
+
rejectionFilterInstalled = true;
|
|
72
|
+
addEventListener('unhandledrejection', (e) => { if (isRedirect(e.reason)) e.preventDefault(); });
|
|
73
|
+
}
|
|
74
|
+
let target;
|
|
75
|
+
try { target = new URL(url, location.href); } catch { target = null; }
|
|
76
|
+
if (target && target.origin === location.origin) {
|
|
77
|
+
Promise.resolve(navigate(target.pathname + target.search + target.hash, { replace })).catch(() => location.assign(target.href));
|
|
78
|
+
} else if (target) {
|
|
79
|
+
if (replace) location.replace(target.href);
|
|
80
|
+
else location.assign(target.href);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return Promise.reject(err);
|
|
84
|
+
}
|
|
@@ -165,7 +165,7 @@ export function scanSeeds(root, opts) {
|
|
|
165
165
|
|
|
166
166
|
// The live document can still be holding the OUTGOING page's block, because
|
|
167
167
|
// the initial scan is lazy (it runs on the first `takeSeed`) and a page whose
|
|
168
|
-
// async components all
|
|
168
|
+
// async components are all server-only never triggers it, while the block sits after
|
|
169
169
|
// the body content outside every boundary range, so no swap removes it either.
|
|
170
170
|
//
|
|
171
171
|
// Strip it WITHOUT ingesting. Those values belong to a page that is being
|
|
@@ -173,7 +173,7 @@ export function scanSeeds(root, opts) {
|
|
|
173
173
|
// them for the lazy scan to ingest LATER is what let a departed page's value
|
|
174
174
|
// win a key under last-write-wins and contradict the paint. Ingesting them
|
|
175
175
|
// HERE would order them correctly but keep them forever, because the very
|
|
176
|
-
// reason the block went unconsumed is that its components
|
|
176
|
+
// reason the block went unconsumed is that its components never ship, so nothing
|
|
177
177
|
// will ever call `takeSeed` for those keys and nothing but a hit deletes one.
|
|
178
178
|
// That would grow the store by a whole page payload per navigation, in
|
|
179
179
|
// production, and inflate the "still unconsumed" figure in the dev line with
|
|
@@ -183,9 +183,9 @@ export function scanSeeds(root, opts) {
|
|
|
183
183
|
if (incomingPage) {
|
|
184
184
|
drainCarriers(live, true);
|
|
185
185
|
// And evict what the outgoing page ALREADY ingested. Stripping the DOM
|
|
186
|
-
// carrier is not enough: a page carrying
|
|
186
|
+
// carrier is not enough: a page carrying a server-only component alongside a
|
|
187
187
|
// shipping one has its WHOLE block ingested by the lazy scan the shipping
|
|
188
|
-
// one triggers, so the
|
|
188
|
+
// one triggers, so the server-only component's keys are in the store with no
|
|
189
189
|
// carrier left to strip and nothing that will ever consume them. Navigate to
|
|
190
190
|
// a page that emits no seed for such a key (a streamed page emits no block
|
|
191
191
|
// at all) and a component calling the same action with the same arguments
|
|
@@ -424,7 +424,7 @@ export function takeSeed(hash, fnName, argsKey) {
|
|
|
424
424
|
* `replaced` are what `scanSeeds` merged; `hits` and `misses` are what the
|
|
425
425
|
* generated RPC stubs asked for; `pending` is what is still in the store
|
|
426
426
|
* unconsumed (a non-zero `pending` at rest usually means the seeding component
|
|
427
|
-
*
|
|
427
|
+
* is server-only, so nothing on the client was ever going to call it).
|
|
428
428
|
* `keyMisses` is the provable subset of `misses`: a call for an action this page
|
|
429
429
|
* DID seed under other arguments, which is the only miss the client can call a
|
|
430
430
|
* defect (see `seedFns`).
|
package/src/asset-url.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Resolve a `public/` asset url
|
|
2
|
+
* Resolve a `public/` asset url (served at the url ROOT: `public/app.css` is
|
|
3
|
+
* `/app.css`, as in Next.js) to its content-fingerprinted form, so a
|
|
3
4
|
* deploy that changes the file changes the url and no cache can serve the
|
|
4
5
|
* previous bytes.
|
|
5
6
|
*
|
|
6
7
|
* import { html, asset } from '@webjsdev/core';
|
|
7
|
-
* html`<link rel="stylesheet" href=${asset('/
|
|
8
|
+
* html`<link rel="stylesheet" href=${asset('/app.css')}>`
|
|
8
9
|
*
|
|
9
10
|
* On the server, `@webjsdev/server` installs a provider that appends
|
|
10
11
|
* `?v=<content-hash>`; the framework then serves a `?v=`-carrying request
|
|
@@ -18,7 +19,7 @@
|
|
|
18
19
|
* the hydrate marker and `createInstance` replaces the SSR'd children), so
|
|
19
20
|
* the client value overwrites the server one. With no provider in the
|
|
20
21
|
* browser, `asset()` returns the bare path there, so an `<img src=${asset(
|
|
21
|
-
* '/
|
|
22
|
+
* '/logo.svg')}>` inside a shipping component paints the hashed url
|
|
22
23
|
* at SSR and then swaps it for the un-hashed one on upgrade, fetching the
|
|
23
24
|
* same bytes twice and leaving the short-lived url in the DOM. It still
|
|
24
25
|
* WORKS (the path is always valid), which is why this is a convention rather
|
|
@@ -28,13 +29,11 @@
|
|
|
28
29
|
* The sibling `cspNonce()` carries the same server/browser asymmetry and the
|
|
29
30
|
* same scoping, for the same hydration reason.
|
|
30
31
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* into the browser bundle for no benefit. Resolver ordering is not the issue
|
|
35
|
-
* (the server installs its resolver at boot, before any app module loads).
|
|
32
|
+
* Calling it at module scope works too: the server installs its resolver at
|
|
33
|
+
* boot, before any app module loads, so a hoisted `const CSS =
|
|
34
|
+
* asset('/app.css')` in a page or layout resolves the same as one in render.
|
|
36
35
|
*
|
|
37
|
-
* Under `webjs.basePath`, write the prefix yourself: `asset('/app/
|
|
36
|
+
* Under `webjs.basePath`, write the prefix yourself: `asset('/app/x.css')`.
|
|
38
37
|
* The framework only base-path-prefixes urls IT emits, so an author-written
|
|
39
38
|
* url is already your responsibility (an un-prefixed one 404s under a
|
|
40
39
|
* sub-path deploy with or without this helper). `asset()` strips the prefix
|
|
@@ -58,9 +57,9 @@
|
|
|
58
57
|
* exactly the set of urls someone deliberately marked and nothing else in the
|
|
59
58
|
* document is ever read or rewritten.
|
|
60
59
|
*
|
|
61
|
-
* Scope note: only a `public/`
|
|
62
|
-
* static-asset route will actually serve. Anything else is
|
|
63
|
-
* unchanged rather than guessed at.
|
|
60
|
+
* Scope note: only a file that exists under `public/` is fingerprinted,
|
|
61
|
+
* matching what the static-asset route will actually serve. Anything else is
|
|
62
|
+
* returned unchanged rather than guessed at.
|
|
64
63
|
*/
|
|
65
64
|
|
|
66
65
|
/** @type {((path: string) => string) | null} */
|
|
@@ -85,7 +84,7 @@ export function setAssetUrlProvider(fn) {
|
|
|
85
84
|
* resolved. Every failure mode degrades to the plain path, never to a broken
|
|
86
85
|
* one.
|
|
87
86
|
*
|
|
88
|
-
* @param {string} path a root-absolute same-origin path, e.g. `/
|
|
87
|
+
* @param {string} path a root-absolute same-origin path, e.g. `/app.css`
|
|
89
88
|
* @returns {string}
|
|
90
89
|
*/
|
|
91
90
|
export function asset(path) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Directive } from './directive.js';
|
|
2
|
+
import type { PartInfo } from './directive.js';
|
|
3
|
+
|
|
4
|
+
export { directive, Directive, PartType } from './directive.js';
|
|
5
|
+
export type { PartInfo, Part, DirectiveResult, DirectiveParameters } from './directive.js';
|
|
6
|
+
|
|
7
|
+
/** lit's `AsyncDirective`: can `setValue()` outside a render and observes connection. */
|
|
8
|
+
export abstract class AsyncDirective extends Directive {
|
|
9
|
+
constructor(partInfo: PartInfo);
|
|
10
|
+
/** Whether the host component is in the document. */
|
|
11
|
+
isConnected: boolean;
|
|
12
|
+
/** Commit a value to the part outside a render. A no-op on the server. */
|
|
13
|
+
setValue(value: unknown): void;
|
|
14
|
+
/** The host left the document, or the part was torn down. */
|
|
15
|
+
protected disconnected(): void;
|
|
16
|
+
/** The host came back. */
|
|
17
|
+
protected reconnected(): void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async directive authoring, lit's `lit/async-directive.js` under a WebJs
|
|
3
|
+
* path: `@webjsdev/core/async-directive`.
|
|
4
|
+
*
|
|
5
|
+
* Portions derived from Lit, Copyright Google LLC, BSD-3-Clause.
|
|
6
|
+
*
|
|
7
|
+
* An `AsyncDirective` can push a value into its part outside a render with
|
|
8
|
+
* `setValue()`, and is told when the component that rendered it leaves the
|
|
9
|
+
* document (`disconnected()`) and comes back (`reconnected()`), so it can drop
|
|
10
|
+
* and re-establish a subscription.
|
|
11
|
+
*
|
|
12
|
+
* ```js
|
|
13
|
+
* import { AsyncDirective, directive } from '@webjsdev/core/async-directive';
|
|
14
|
+
*
|
|
15
|
+
* class Ticker extends AsyncDirective {
|
|
16
|
+
* render(ms) {
|
|
17
|
+
* this.#start(ms);
|
|
18
|
+
* return 0;
|
|
19
|
+
* }
|
|
20
|
+
* #start(ms) {
|
|
21
|
+
* clearInterval(this.id);
|
|
22
|
+
* let n = 0;
|
|
23
|
+
* this.id = setInterval(() => this.setValue(++n), ms);
|
|
24
|
+
* }
|
|
25
|
+
* disconnected() { clearInterval(this.id); }
|
|
26
|
+
* reconnected() { this.#start(1000); }
|
|
27
|
+
* }
|
|
28
|
+
* export const ticker = directive(Ticker);
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* Connection follows the HOST component (the one whose template holds the
|
|
32
|
+
* part), plus the part's own lifetime: a directive whose part is torn down, or
|
|
33
|
+
* replaced by a different directive class, is disconnected for good. On the
|
|
34
|
+
* server `setValue()` does nothing and only `render()` runs.
|
|
35
|
+
*
|
|
36
|
+
* @module async-directive
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { Directive } from './directive.js';
|
|
40
|
+
|
|
41
|
+
export { directive, Directive, PartType } from './directive.js';
|
|
42
|
+
|
|
43
|
+
export class AsyncDirective extends Directive {
|
|
44
|
+
/** @param {{ type: number, name?: string, tagName?: string, strings?: readonly string[] }} partInfo */
|
|
45
|
+
constructor(partInfo) {
|
|
46
|
+
super(partInfo);
|
|
47
|
+
/**
|
|
48
|
+
* Whether the directive's host is in the document. `true` until the
|
|
49
|
+
* renderer reports otherwise.
|
|
50
|
+
* @type {boolean}
|
|
51
|
+
*/
|
|
52
|
+
this.isConnected = true;
|
|
53
|
+
/**
|
|
54
|
+
* Installed by the client renderer: commits a value to this directive's
|
|
55
|
+
* part. Absent at SSR, where `setValue()` is a no-op.
|
|
56
|
+
* @type {((value: unknown) => void) | undefined}
|
|
57
|
+
*/
|
|
58
|
+
this._$webjsCommit = undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Commit `value` to the part outside a render, as if the directive had
|
|
63
|
+
* returned it. A no-op on the server. As in lit, stop calling it once
|
|
64
|
+
* `disconnected()` runs.
|
|
65
|
+
* @param {unknown} value
|
|
66
|
+
*/
|
|
67
|
+
setValue(value) {
|
|
68
|
+
if (this._$webjsCommit) this._$webjsCommit(value);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Called by the renderer when the connection state changes; dispatches to
|
|
73
|
+
* `disconnected()` / `reconnected()` once per transition.
|
|
74
|
+
* @param {boolean} connected
|
|
75
|
+
*/
|
|
76
|
+
_$webjsSetConnected(connected) {
|
|
77
|
+
if (connected === this.isConnected) return;
|
|
78
|
+
this.isConnected = connected;
|
|
79
|
+
if (connected) this.reconnected();
|
|
80
|
+
else this.disconnected();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The host left the document, or the part was torn down. Release resources. */
|
|
84
|
+
disconnected() {}
|
|
85
|
+
|
|
86
|
+
/** The host came back. Re-establish what `disconnected()` released. */
|
|
87
|
+
reconnected() {}
|
|
88
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { nothing } from './sentinels.js';
|
|
2
|
+
export interface ClassMapDirective { readonly _$webjs: 'class-map'; readonly value: Record<string, unknown>; }
|
|
3
|
+
export interface StyleMapDirective { readonly _$webjs: 'style-map'; readonly value: Record<string, string | number | null | undefined>; }
|
|
4
|
+
export function classMap(classes: Record<string, unknown>): ClassMapDirective;
|
|
5
|
+
export function styleMap(styles: Record<string, string | number | null | undefined>): StyleMapDirective;
|
|
6
|
+
export function ifDefined<T>(value: T): NonNullable<T> | typeof nothing;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { nothing } from './sentinels.js';
|
|
2
|
+
|
|
3
|
+
/** Map class names to whether each should be present. @param {Record<string, unknown>} classes */
|
|
4
|
+
export function classMap(classes) { return { _$webjs: 'class-map', value: classes }; }
|
|
5
|
+
/** Map CSS property names to their values. @param {Record<string, string | number | null | undefined>} styles */
|
|
6
|
+
export function styleMap(styles) { return { _$webjs: 'style-map', value: styles }; }
|
|
7
|
+
/** Remove an attribute when its value is nullish. @template T @param {T} value */
|
|
8
|
+
export function ifDefined(value) { return value ?? nothing; }
|
|
9
|
+
|
|
10
|
+
export function isAttributeMap(value) {
|
|
11
|
+
return value && (value._$webjs === 'class-map' || value._$webjs === 'style-map');
|
|
12
|
+
}
|
|
13
|
+
export function cssName(name) { return name.startsWith('--') ? name : name.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase()); }
|
|
14
|
+
export function attributeMapText(value, name) {
|
|
15
|
+
const classes = value._$webjs === 'class-map';
|
|
16
|
+
if (name !== (classes ? 'class' : 'style')) throw new Error('classMap/styleMap must be used in a class/style attribute.');
|
|
17
|
+
// Lit deliberately pads even an empty map to separate adjacent static classes.
|
|
18
|
+
return classes
|
|
19
|
+
? ' ' + Object.keys(value.value).filter((key) => value.value[key]).join(' ') + ' '
|
|
20
|
+
: Object.entries(value.value).filter(([, v]) => v != null).map(([key, v]) => `${cssName(key)}:${v};`).join('');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Commit only the map-owned tokens, preserving static and externally added values. */
|
|
24
|
+
export function applyAttributeMap(part, value) {
|
|
25
|
+
if (part.kind !== 'attr' && part.kind !== 'attr-mixed') throw new Error('classMap/styleMap require an attribute binding.');
|
|
26
|
+
const classes = value._$webjs === 'class-map';
|
|
27
|
+
if (part.name !== (classes ? 'class' : 'style')) throw new Error('classMap/styleMap must be used in a class/style attribute.');
|
|
28
|
+
if (part.group && part.group.length !== 1) throw new Error('classMap/styleMap require a single expression.');
|
|
29
|
+
if (part.__mapKind !== value._$webjs) {
|
|
30
|
+
part.el.setAttribute(part.name, (part.statics || []).join(classes ? ' ' : ''));
|
|
31
|
+
part.__mapKind = value._$webjs;
|
|
32
|
+
part.__mapKeys = new Set();
|
|
33
|
+
part.__staticClasses = classes ? new Set(part.el.classList) : new Set();
|
|
34
|
+
}
|
|
35
|
+
const next = new Set();
|
|
36
|
+
for (const [key, v] of Object.entries(value.value)) {
|
|
37
|
+
if (classes) {
|
|
38
|
+
if (v && !part.__staticClasses.has(key)) { part.el.classList.add(key); next.add(key); }
|
|
39
|
+
} else if (v != null) {
|
|
40
|
+
const name = cssName(key); const text = String(v);
|
|
41
|
+
part.el.style.setProperty(name, text.replace(/\s*!important\s*$/, ''), /!important\s*$/.test(text) ? 'important' : '');
|
|
42
|
+
next.add(name);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (const key of part.__mapKeys) if (!next.has(key)) {
|
|
46
|
+
if (classes) part.el.classList.remove(key); else part.el.style.removeProperty(key);
|
|
47
|
+
}
|
|
48
|
+
part.__mapKeys = next;
|
|
49
|
+
}
|