@excom/neutron 0.1.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/.rush/temp/chunked-rush-logs/neutron.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/neutron.build_package-metas.chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/all.log +1 -0
- package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/state.json +3 -0
- package/.rush/temp/operation/build_package-metas/all.log +1 -0
- package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_package-metas/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +3 -0
- package/config/rig.json +6 -0
- package/index.ts +9 -0
- package/package.json +45 -0
- package/rush-logs/neutron.apply-exports.cache.log +1 -0
- package/rush-logs/neutron.apply-exports.log +1 -0
- package/rush-logs/neutron.build_package-metas.cache.log +1 -0
- package/rush-logs/neutron.build_package-metas.log +1 -0
- package/src/command.ts +102 -0
- package/src/common-element.ts +377 -0
- package/src/constants.ts +101 -0
- package/src/devtools-hook.ts +93 -0
- package/src/lifecycle-configs.ts +304 -0
- package/src/neutron-element.ts +72 -0
- package/src/neutron-error.ts +6 -0
- package/src/neutron-internal.ts +550 -0
- package/src/neutron.ts +36 -0
- package/src/types/effect.types.ts +104 -0
- package/src/types/element.types.ts +263 -0
- package/src/types/index.ts +4 -0
- package/src/types/new.types.ts +159 -0
- package/src/types/shared.types.ts +25 -0
- package/src/utils/effect.ts +357 -0
- package/src/utils/element.ts +382 -0
- package/src/utils/index.ts +2 -0
- package/support/docs/COMMANDS.md +58 -0
- package/support/docs/COMPOSE.md +32 -0
- package/support/docs/DEBUG.md +11 -0
- package/support/docs/DEFINE.md +20 -0
- package/support/docs/EFFECTS.md +49 -0
- package/support/docs/EVENTS.md +57 -0
- package/support/docs/LIFECYCLES.md +69 -0
- package/support/docs/METHODS.md +49 -0
- package/support/docs/PROMISE_PROPS.md +29 -0
- package/support/docs/PROPS.md +64 -0
- package/support/docs/PROP_REACTIONS.md +35 -0
- package/support/docs/PROVISION.md +31 -0
- package/support/docs/README.md +118 -0
- package/support/docs/RECOMPOSE.md +70 -0
- package/support/docs/TYPESCRIPT.md +51 -0
- package/support/docs-sections.json +42 -0
- package/support/package-meta.json +129 -0
- package/support/tests/commands.test.ts +330 -0
- package/support/tests/common-element.test.ts +342 -0
- package/support/tests/devtools-hook.test.ts +209 -0
- package/support/tests/devtools-renderer.test.ts +125 -0
- package/support/tests/effects.test.ts +253 -0
- package/support/tests/element-config.test.ts +331 -0
- package/support/tests/entry.test.ts +68 -0
- package/support/tests/lifecycles.test.ts +489 -0
- package/support/tests/loop-guard.test.ts +162 -0
- package/support/tests/neutron.test.ts +1286 -0
- package/support/tests/recompose.test.ts +129 -0
- package/support/tests/utils.test.ts +75 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type DevtoolsRenderer,
|
|
3
|
+
injectRenderersIfNeeded,
|
|
4
|
+
NUCLEUS_DEVTOOLS_HOOK_VERSION,
|
|
5
|
+
registerRenderer,
|
|
6
|
+
toDevtoolsJson,
|
|
7
|
+
} from "@excom/kit-devtools";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Nucleus DevTools probe. The hook lives in `@excom/kit-devtools`;
|
|
11
|
+
* this re-exports it and adds the Neutron renderer.
|
|
12
|
+
*
|
|
13
|
+
* publicize(["neutron", "defined"], { tag, props }): once per `setup()`,
|
|
14
|
+
* no `weakElement`; extension audits attr names from it
|
|
15
|
+
* publicize(["neutron", "constructed"], { weakElement, tag })
|
|
16
|
+
* publicize(["neutron", "connected"], { weakElement, tag, isMoving, isFirstMount })
|
|
17
|
+
* publicize(["neutron", "disconnected"], { weakElement, tag, isMoving })
|
|
18
|
+
* publicize(["neutron", "effect"], { weakElement, tag, signature, effect, lockDepth, … })
|
|
19
|
+
* publicize(["neutron", "commit"], { weakElement, tag, changedProps })
|
|
20
|
+
* publicize(["neutron", "error"], { weakElement, tag, errorMessage, errorName })
|
|
21
|
+
*/
|
|
22
|
+
export {
|
|
23
|
+
attachDevtools,
|
|
24
|
+
DEVTOOLS_SERIALIZERS,
|
|
25
|
+
type DevtoolsHook,
|
|
26
|
+
type DevtoolsRenderer,
|
|
27
|
+
type DevtoolsRendererKind,
|
|
28
|
+
getDevtoolsHook,
|
|
29
|
+
NUCLEUS_DEVTOOLS_HOOK_KEY,
|
|
30
|
+
NUCLEUS_DEVTOOLS_HOOK_VERSION,
|
|
31
|
+
pathMatches,
|
|
32
|
+
publicize,
|
|
33
|
+
type PublicizeMeta,
|
|
34
|
+
type PublicizePath,
|
|
35
|
+
} from "@excom/kit-devtools";
|
|
36
|
+
|
|
37
|
+
export type NeutronInspectSnapshot = {
|
|
38
|
+
tag: string;
|
|
39
|
+
id: string | null;
|
|
40
|
+
isMounted: boolean;
|
|
41
|
+
wasMounted: boolean;
|
|
42
|
+
isMoving: boolean;
|
|
43
|
+
isAdopted: boolean;
|
|
44
|
+
propNames: string[];
|
|
45
|
+
/** Primitive-safe prop values. Element/object refs are tagged, not retained. */
|
|
46
|
+
props: Record<string, unknown>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type NeutronRenderer = DevtoolsRenderer & {
|
|
50
|
+
kind: "neutron";
|
|
51
|
+
walkRoot: () => Element;
|
|
52
|
+
isNeutronElement: (el: Element) => boolean;
|
|
53
|
+
inspect: (el: Element) => NeutronInspectSnapshot | null;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const renderer: NeutronRenderer = {
|
|
57
|
+
version: NUCLEUS_DEVTOOLS_HOOK_VERSION,
|
|
58
|
+
kind: "neutron",
|
|
59
|
+
walkRoot: () => document.body,
|
|
60
|
+
isNeutronElement: (el) => !!(el as any)?._n_?.ctr,
|
|
61
|
+
inspect: (el) => {
|
|
62
|
+
const internal = (el as any)?._n_;
|
|
63
|
+
if (!internal?.ctr) return null;
|
|
64
|
+
const propNames = Object.keys(internal.ctr.runtimeConfig?.props ?? {});
|
|
65
|
+
const props: Record<string, unknown> = {};
|
|
66
|
+
for (const name of propNames) {
|
|
67
|
+
try {
|
|
68
|
+
props[name] = toDevtoolsJson(el[name as keyof typeof el]);
|
|
69
|
+
} catch {
|
|
70
|
+
props[name] = "[unreadable]";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
tag: el.localName,
|
|
75
|
+
id: el.id || null,
|
|
76
|
+
isMounted: !!el["isMounted" as keyof typeof el],
|
|
77
|
+
wasMounted: !!el["wasMounted" as keyof typeof el],
|
|
78
|
+
isMoving: !!el["isMoving" as keyof typeof el],
|
|
79
|
+
isAdopted: !!el["isAdopted" as keyof typeof el],
|
|
80
|
+
propNames,
|
|
81
|
+
props,
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
registerRenderer(renderer);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Inject the Neutron renderer into an installed hook (no-op when none, or
|
|
90
|
+
* when already injected). Called on the first `define()` after a hook is
|
|
91
|
+
* installed at `document_start`; late attachers use `attachDevtools()`.
|
|
92
|
+
*/
|
|
93
|
+
export const injectRendererIfNeeded = injectRenderersIfNeeded;
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { isCustomCommand, TCommandEvent } from "./command";
|
|
2
|
+
import { NeutronError } from "./neutron-error";
|
|
3
|
+
import type { NeutronInternal as NeutronInternalType } from "./neutron-internal";
|
|
4
|
+
import { AnyFunction, EffectorOptions } from "./types";
|
|
5
|
+
import { isChildEffect, setDebugLifecycleSignature } from "./utils";
|
|
6
|
+
import {
|
|
7
|
+
Converter,
|
|
8
|
+
ExecHandlerFn,
|
|
9
|
+
toArray,
|
|
10
|
+
unique,
|
|
11
|
+
} from "@excom/kit-utils";
|
|
12
|
+
|
|
13
|
+
interface TLifecycleConfig {
|
|
14
|
+
key: string;
|
|
15
|
+
isBatched: boolean;
|
|
16
|
+
batchPrefix?: string;
|
|
17
|
+
triggerFn?: (
|
|
18
|
+
instance: NeutronInternalType,
|
|
19
|
+
batchPrefix: string,
|
|
20
|
+
name: string
|
|
21
|
+
) => void;
|
|
22
|
+
batchNames?: (names: string[]) => string[];
|
|
23
|
+
batchExecFn?: ExecHandlerFn;
|
|
24
|
+
effectorOptions?: (nameArray: string[]) => EffectorOptions;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
* Order matters: several lifecycles share batch names, and earlier
|
|
29
|
+
* entries run first. `.onConnected()` must beat `.onPropSet("isMounted")`.
|
|
30
|
+
*/
|
|
31
|
+
export const LifecycleConfigs: TLifecycleConfig[] = [
|
|
32
|
+
{
|
|
33
|
+
key: "constructed",
|
|
34
|
+
isBatched: true,
|
|
35
|
+
batchNames: () => ["message:constructed"],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: "connected",
|
|
39
|
+
isBatched: true,
|
|
40
|
+
batchNames: () => ["isMounted"],
|
|
41
|
+
effectorOptions: () => ({
|
|
42
|
+
validateInput: ([element]) => element.isMounted && [element],
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
key: "adopted",
|
|
47
|
+
isBatched: true,
|
|
48
|
+
batchNames: () => ["isAdopted"],
|
|
49
|
+
effectorOptions: () => ({
|
|
50
|
+
validateInput: ([element]) => element.isAdopted && [element],
|
|
51
|
+
}),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
key: "error",
|
|
55
|
+
isBatched: true,
|
|
56
|
+
batchNames: () => ["message:error"],
|
|
57
|
+
effectorOptions: () => ({
|
|
58
|
+
validateInput: ([element, notifs]) => [element, notifs["message:error"]],
|
|
59
|
+
}),
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
key: "effect",
|
|
63
|
+
batchNames: (nameArray) => [...nameArray, "message:first-mount"],
|
|
64
|
+
isBatched: true,
|
|
65
|
+
effectorOptions: (nameArray) => ({
|
|
66
|
+
validateInput: ([element, previous]) =>
|
|
67
|
+
(element.isMounted || element.wasMounted) &&
|
|
68
|
+
!!nameArray.find((name) => name in previous) && [element, previous],
|
|
69
|
+
}),
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
key: "propUnset",
|
|
73
|
+
isBatched: true,
|
|
74
|
+
batchNames: (nameArray) => [...nameArray, "message:first-mount"],
|
|
75
|
+
effectorOptions: (nameArray) => ({
|
|
76
|
+
validateInput: ([element, previous]) =>
|
|
77
|
+
(element.isMounted || element.wasMounted) &&
|
|
78
|
+
!!Object.keys(previous).find(
|
|
79
|
+
(name) =>
|
|
80
|
+
nameArray.includes(name) &&
|
|
81
|
+
!(
|
|
82
|
+
Converter.type(
|
|
83
|
+
element._n_.ctr.CustomElement.getPropConfig({ prop: name })!
|
|
84
|
+
.type
|
|
85
|
+
)?.prop.isTruthy(element[name]) ?? element[name]
|
|
86
|
+
)
|
|
87
|
+
) && [element, previous],
|
|
88
|
+
validateOutput: ([element], mutation) =>
|
|
89
|
+
nameArray.every((name) => {
|
|
90
|
+
if (
|
|
91
|
+
name in mutation &&
|
|
92
|
+
!mutation[name] &&
|
|
93
|
+
!isChildEffect(
|
|
94
|
+
mutation[name],
|
|
95
|
+
element._n_.ctr.CustomElement.getPropConfig({ prop: name })!
|
|
96
|
+
)
|
|
97
|
+
) {
|
|
98
|
+
throw new NeutronError(
|
|
99
|
+
`Cannot unset prop ${name} in its own onPropUnset handler.`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
}),
|
|
104
|
+
}),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
key: "propSet",
|
|
108
|
+
isBatched: true,
|
|
109
|
+
batchNames: (nameArray) => [...nameArray, "message:first-mount"],
|
|
110
|
+
effectorOptions: (nameArray) => ({
|
|
111
|
+
validateInput: ([element, previous]) =>
|
|
112
|
+
(element.isMounted || element.wasMounted) &&
|
|
113
|
+
!!Object.keys(previous).find(
|
|
114
|
+
(name) =>
|
|
115
|
+
nameArray.includes(name) &&
|
|
116
|
+
(Converter.type(
|
|
117
|
+
element._n_.ctr.CustomElement.getPropConfig({ prop: name })!.type
|
|
118
|
+
)?.prop.isTruthy(element[name]) ??
|
|
119
|
+
!!element[name])
|
|
120
|
+
) && [element, previous],
|
|
121
|
+
validateOutput: ([element], mutation) =>
|
|
122
|
+
nameArray.every((name) => {
|
|
123
|
+
// child-element effects in own handlers are ok: `.onPropSet("myElement", () => ({myElement: {addListener: [...]}}))`
|
|
124
|
+
if (
|
|
125
|
+
mutation[name] &&
|
|
126
|
+
!isChildEffect(
|
|
127
|
+
mutation[name],
|
|
128
|
+
element._n_.ctr.CustomElement.getPropConfig({ prop: name })!
|
|
129
|
+
)
|
|
130
|
+
) {
|
|
131
|
+
throw new NeutronError(
|
|
132
|
+
`Cannot set prop ${name} in its own onPropSet handler.`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return true;
|
|
136
|
+
}),
|
|
137
|
+
}),
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
key: "propChanged",
|
|
141
|
+
isBatched: true,
|
|
142
|
+
batchNames: (nameArray) => [...nameArray, "message:first-mount"],
|
|
143
|
+
effectorOptions: (nameArray) => ({
|
|
144
|
+
validateInput: ([element, previous]) =>
|
|
145
|
+
(element.isMounted || element.wasMounted) &&
|
|
146
|
+
!!nameArray.find((name) => name in previous) && [element, previous],
|
|
147
|
+
validateOutput: ([element], mutation) =>
|
|
148
|
+
nameArray.every((name) => {
|
|
149
|
+
if (
|
|
150
|
+
name in mutation &&
|
|
151
|
+
!isChildEffect(
|
|
152
|
+
mutation[name],
|
|
153
|
+
element._n_.ctr.CustomElement.getPropConfig({ prop: name })!
|
|
154
|
+
)
|
|
155
|
+
) {
|
|
156
|
+
throw new NeutronError(
|
|
157
|
+
`Cannot change prop ${name} in its own onPropChanged handler.`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
}),
|
|
162
|
+
}),
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
key: "promiseResolved",
|
|
166
|
+
isBatched: true,
|
|
167
|
+
batchPrefix: "promise:resolved:",
|
|
168
|
+
triggerFn: linkPromiseToBatch("onResolved"),
|
|
169
|
+
effectorOptions: () => ({
|
|
170
|
+
validateInput: validatePromiseArgs("promise:resolved:"),
|
|
171
|
+
}),
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
key: "promiseRejected",
|
|
175
|
+
isBatched: true,
|
|
176
|
+
batchPrefix: "promise:rejected:",
|
|
177
|
+
triggerFn: linkPromiseToBatch("onRejected"),
|
|
178
|
+
effectorOptions: () => ({
|
|
179
|
+
validateInput: validatePromiseArgs("promise:rejected:"),
|
|
180
|
+
}),
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
key: "broadcast",
|
|
184
|
+
isBatched: false,
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
key: "event",
|
|
188
|
+
isBatched: false,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
key: "eventDefault",
|
|
192
|
+
isBatched: false,
|
|
193
|
+
effectorOptions: () => ({
|
|
194
|
+
delayNextTask: true,
|
|
195
|
+
validateInput: ([element, e]: [any, Event]) =>
|
|
196
|
+
element === e.target && !e.defaultPrevented && [element, e],
|
|
197
|
+
}),
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
/*
|
|
201
|
+
* `command` events (HTML Command API) never bubble; the at-target
|
|
202
|
+
* check only guards against a hand-made bubbling `Event("command")`.
|
|
203
|
+
* Runs in a microtask so every listener of the dispatch may still
|
|
204
|
+
* `preventDefault()`, yet the user activation that invoked it (a
|
|
205
|
+
* click) is intact for permission prompts and popups.
|
|
206
|
+
*/
|
|
207
|
+
key: "command",
|
|
208
|
+
isBatched: false,
|
|
209
|
+
effectorOptions: (names) => ({
|
|
210
|
+
delayMicrotask: true,
|
|
211
|
+
validateInput: ([element, e]: [any, TCommandEvent]) =>
|
|
212
|
+
element === e.target &&
|
|
213
|
+
!e.defaultPrevented &&
|
|
214
|
+
names.includes(e.command) && [element, e],
|
|
215
|
+
}),
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
key: "disconnected",
|
|
219
|
+
isBatched: true,
|
|
220
|
+
batchNames: () => ["isMounted"],
|
|
221
|
+
effectorOptions: () => ({
|
|
222
|
+
validateInput: ([element]) => !element.isMounted && [element],
|
|
223
|
+
}),
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
export const LifecycleConfigMap: Record<
|
|
228
|
+
TLifecycleConfig["key"],
|
|
229
|
+
TLifecycleConfig
|
|
230
|
+
> = LifecycleConfigs.reduce(
|
|
231
|
+
(acc, config) => {
|
|
232
|
+
acc[config.key] = config;
|
|
233
|
+
return acc;
|
|
234
|
+
},
|
|
235
|
+
{} as Record<TLifecycleConfig["key"], TLifecycleConfig>
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
function linkPromiseToBatch(method) {
|
|
239
|
+
return (instance, batchPrefix, name) => {
|
|
240
|
+
instance.queueManager.getQueue(name)[method]((state) => {
|
|
241
|
+
instance.batchManager.notify(batchPrefix + name, state.value);
|
|
242
|
+
});
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function validatePromiseArgs(batchPrefix: string) {
|
|
246
|
+
return ([element, result]) => [
|
|
247
|
+
element,
|
|
248
|
+
Object.fromEntries(
|
|
249
|
+
Object.entries(result as Record<string, unknown>)
|
|
250
|
+
.filter(([key]) => key.startsWith(batchPrefix))
|
|
251
|
+
.map(([key, value]) => [key.replace(/(.*):/, ""), value])
|
|
252
|
+
),
|
|
253
|
+
];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export const registerLifecycle = (
|
|
257
|
+
ctr: typeof NeutronInternalType,
|
|
258
|
+
lifecycleName: TLifecycleConfig["key"],
|
|
259
|
+
nameArg: string | string[],
|
|
260
|
+
fn: AnyFunction
|
|
261
|
+
) => {
|
|
262
|
+
const nameArray = unique(toArray(nameArg));
|
|
263
|
+
if (lifecycleName === "command") {
|
|
264
|
+
nameArray.forEach((name) => {
|
|
265
|
+
if (!isCustomCommand(name)) {
|
|
266
|
+
throw new NeutronError(
|
|
267
|
+
`Command names must start with "--" (got "${name}"): built-in commands never reach a custom element.`
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
setDebugLifecycleSignature(fn, lifecycleName, nameArray);
|
|
273
|
+
ctr.builtConfig.lifecycles[lifecycleName].push([nameArray, fn]);
|
|
274
|
+
return ctr;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
export const unregisterLifecycle = (
|
|
278
|
+
ctr: typeof NeutronInternalType,
|
|
279
|
+
lifecycleName: TLifecycleConfig["key"],
|
|
280
|
+
nameArg: string | string[],
|
|
281
|
+
fn: AnyFunction
|
|
282
|
+
) => {
|
|
283
|
+
const nameArray = unique(toArray(nameArg));
|
|
284
|
+
/* Drop the entry when every name matches `entry[0]`; otherwise
|
|
285
|
+
* strip only the matching names from `entry[0]`. */
|
|
286
|
+
ctr.builtConfig.lifecycles[lifecycleName] = ctr.builtConfig.lifecycles[
|
|
287
|
+
lifecycleName
|
|
288
|
+
]
|
|
289
|
+
.map(([_entryNames, entryFn]) => {
|
|
290
|
+
if (entryFn === fn) {
|
|
291
|
+
const entryNames = _entryNames.filter(
|
|
292
|
+
(name) => !nameArray.includes(name)
|
|
293
|
+
);
|
|
294
|
+
if (entryNames.length === 0) {
|
|
295
|
+
return false;
|
|
296
|
+
} else {
|
|
297
|
+
return [entryNames, entryFn];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return [_entryNames, entryFn];
|
|
301
|
+
})
|
|
302
|
+
.filter(Boolean) as [string[], AnyFunction][];
|
|
303
|
+
return ctr;
|
|
304
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { NeutronInternal as TNeutronInternal } from "./neutron-internal";
|
|
2
|
+
import type { PropConfig, RuntimeConfig } from "./types";
|
|
3
|
+
|
|
4
|
+
export class NeutronElement extends HTMLElement {
|
|
5
|
+
static observedAttributes: string[] = [];
|
|
6
|
+
static NeutronInternal: typeof TNeutronInternal;
|
|
7
|
+
|
|
8
|
+
/* --- PUBLIC: STATIC INTROSPECTION --- */
|
|
9
|
+
/**
|
|
10
|
+
* The element's runtime configuration, its `tag`, every prop's
|
|
11
|
+
* `PropConfig` (keyed by prop name: `prop`, `attr`, `type`, defaults,
|
|
12
|
+
* `notify`, …), events, broadcasts, methods and lifecycles, as built by
|
|
13
|
+
* `define()`. `undefined` before the element is defined. Reach it from an
|
|
14
|
+
* instance through its constructor (`el.constructor.getConfig()`) or from
|
|
15
|
+
* the registry (`customElements.get("my-tag").getConfig()`). Read-only by
|
|
16
|
+
* contract: mutating it changes the running definition.
|
|
17
|
+
*/
|
|
18
|
+
static getConfig(): RuntimeConfig | undefined {
|
|
19
|
+
return this.NeutronInternal?.runtimeConfig;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* One prop's `PropConfig`, looked up by attribute name (`{ attr: "is-open" }`)
|
|
23
|
+
* or prop name (`{ prop: "isOpen" }`). `undefined` when unknown or before
|
|
24
|
+
* `define()`.
|
|
25
|
+
*/
|
|
26
|
+
static getPropConfig({
|
|
27
|
+
attr,
|
|
28
|
+
prop,
|
|
29
|
+
}: {
|
|
30
|
+
attr?: string;
|
|
31
|
+
prop?: string;
|
|
32
|
+
}): PropConfig | undefined {
|
|
33
|
+
return Object.values(this.getConfig()?.props ?? {}).find((c) =>
|
|
34
|
+
attr ? c.attr === attr : c.prop === prop
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
_n_: TNeutronInternal;
|
|
38
|
+
isMounted: boolean;
|
|
39
|
+
isAdopted: boolean;
|
|
40
|
+
wasMounted: boolean;
|
|
41
|
+
isMoving: boolean;
|
|
42
|
+
renderRoot?: HTMLElement;
|
|
43
|
+
|
|
44
|
+
constructor() {
|
|
45
|
+
super();
|
|
46
|
+
const NeutronInternal = (
|
|
47
|
+
this.constructor as unknown as typeof NeutronElement
|
|
48
|
+
).NeutronInternal;
|
|
49
|
+
this._n_ = new NeutronInternal(this);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Native lifecycle callbacks */
|
|
53
|
+
connectedCallback() {
|
|
54
|
+
this._n_.connectedCallback();
|
|
55
|
+
}
|
|
56
|
+
connectedMoveCallback() {
|
|
57
|
+
this._n_.connectedMoveCallback();
|
|
58
|
+
}
|
|
59
|
+
adoptedCallback() {
|
|
60
|
+
this._n_.adoptedCallback();
|
|
61
|
+
}
|
|
62
|
+
disconnectedCallback() {
|
|
63
|
+
this._n_.disconnectedCallback();
|
|
64
|
+
}
|
|
65
|
+
attributeChangedCallback(
|
|
66
|
+
name: string,
|
|
67
|
+
oldValue: string | null,
|
|
68
|
+
newValue: string | null
|
|
69
|
+
) {
|
|
70
|
+
this._n_.attributeChangedCallback(name, oldValue, newValue);
|
|
71
|
+
}
|
|
72
|
+
}
|