@radishland/runtime 0.0.8 → 0.1.1
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/client/boot.d.ts +2 -0
- package/client/boot.js +148 -0
- package/client/index.d.ts +40 -24
- package/client/index.js +96 -174
- package/client/utils.d.ts +30 -0
- package/client/utils.js +123 -0
- package/package.json +21 -2
package/client/boot.d.ts
ADDED
package/client/boot.js
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
// src/utils.ts
|
2
|
+
var spaces_sep_by_comma = /\s*,\s*/;
|
3
|
+
var bindingConfig = {
|
4
|
+
"checked": {
|
5
|
+
element: ["input"],
|
6
|
+
type: ["boolean"],
|
7
|
+
event: "change"
|
8
|
+
},
|
9
|
+
"value": {
|
10
|
+
element: ["input", "select", "textarea"],
|
11
|
+
type: ["string", "number"],
|
12
|
+
event: "input"
|
13
|
+
}
|
14
|
+
};
|
15
|
+
|
16
|
+
// src/boot.ts
|
17
|
+
var bindingsQueryString = Object.keys(bindingConfig).map(
|
18
|
+
(property) => `[\\@bind\\:${property}]`
|
19
|
+
).join(",");
|
20
|
+
setTimeout(() => {
|
21
|
+
customElements?.whenDefined("handler-registry").then(() => {
|
22
|
+
document.querySelectorAll(
|
23
|
+
`[\\@on],[\\@use],[\\@attr],[\\@attr\\|client],[\\@prop],${bindingsQueryString},[\\@text],[\\@html]`
|
24
|
+
).forEach(
|
25
|
+
(entry) => {
|
26
|
+
const events = entry.getAttribute("@on")?.trim()?.split(spaces_sep_by_comma);
|
27
|
+
if (events) {
|
28
|
+
for (const event of events) {
|
29
|
+
const [type, handler] = event.split(":");
|
30
|
+
const onRequest = new CustomEvent("@on-request", {
|
31
|
+
bubbles: true,
|
32
|
+
cancelable: true,
|
33
|
+
composed: true,
|
34
|
+
detail: {
|
35
|
+
type,
|
36
|
+
handler: handler || type
|
37
|
+
}
|
38
|
+
});
|
39
|
+
entry.dispatchEvent(onRequest);
|
40
|
+
}
|
41
|
+
}
|
42
|
+
const hooks = entry.getAttribute("@use")?.trim()?.split(spaces_sep_by_comma);
|
43
|
+
if (hooks) {
|
44
|
+
for (const hook of hooks) {
|
45
|
+
const useRequest = new CustomEvent("@use-request", {
|
46
|
+
bubbles: true,
|
47
|
+
cancelable: true,
|
48
|
+
composed: true,
|
49
|
+
detail: {
|
50
|
+
hook
|
51
|
+
}
|
52
|
+
});
|
53
|
+
entry.dispatchEvent(useRequest);
|
54
|
+
}
|
55
|
+
}
|
56
|
+
const props = entry.getAttribute("@prop")?.trim().split(spaces_sep_by_comma);
|
57
|
+
if (props) {
|
58
|
+
for (const prop of props) {
|
59
|
+
const [key, value] = prop.split(":");
|
60
|
+
const propRequest = new CustomEvent("@prop-request", {
|
61
|
+
bubbles: true,
|
62
|
+
cancelable: true,
|
63
|
+
composed: true,
|
64
|
+
detail: {
|
65
|
+
property: key,
|
66
|
+
identifier: value || key
|
67
|
+
}
|
68
|
+
});
|
69
|
+
entry.dispatchEvent(propRequest);
|
70
|
+
}
|
71
|
+
}
|
72
|
+
const text = entry.hasAttribute("@text");
|
73
|
+
if (text) {
|
74
|
+
const identifier = entry.getAttribute("@text") || "text";
|
75
|
+
const textRequest = new CustomEvent("@text-request", {
|
76
|
+
bubbles: true,
|
77
|
+
cancelable: true,
|
78
|
+
composed: true,
|
79
|
+
detail: {
|
80
|
+
identifier
|
81
|
+
}
|
82
|
+
});
|
83
|
+
entry.dispatchEvent(textRequest);
|
84
|
+
}
|
85
|
+
const html = entry.hasAttribute("@html");
|
86
|
+
if (html) {
|
87
|
+
const identifier = entry.getAttribute("@html") || "html";
|
88
|
+
const htmlRequest = new CustomEvent("@html-request", {
|
89
|
+
bubbles: true,
|
90
|
+
cancelable: true,
|
91
|
+
composed: true,
|
92
|
+
detail: {
|
93
|
+
identifier
|
94
|
+
}
|
95
|
+
});
|
96
|
+
entry.dispatchEvent(htmlRequest);
|
97
|
+
}
|
98
|
+
const classList = entry.hasAttribute("@class");
|
99
|
+
if (classList) {
|
100
|
+
const identifier = entry.getAttribute("@class") || "class";
|
101
|
+
const classRequest = new CustomEvent("@class-request", {
|
102
|
+
bubbles: true,
|
103
|
+
cancelable: true,
|
104
|
+
composed: true,
|
105
|
+
detail: {
|
106
|
+
identifier
|
107
|
+
}
|
108
|
+
});
|
109
|
+
entry.dispatchEvent(classRequest);
|
110
|
+
}
|
111
|
+
const attributes = [
|
112
|
+
...entry.getAttribute("@attr")?.trim().split(spaces_sep_by_comma) ?? [],
|
113
|
+
...entry.getAttribute("@attr|client")?.trim().split(spaces_sep_by_comma) ?? []
|
114
|
+
];
|
115
|
+
if (attributes.length > 0) {
|
116
|
+
for (const attribute of attributes) {
|
117
|
+
const [key, value] = attribute.split(":");
|
118
|
+
const attrRequest = new CustomEvent("@attr-request", {
|
119
|
+
bubbles: true,
|
120
|
+
cancelable: true,
|
121
|
+
composed: true,
|
122
|
+
detail: {
|
123
|
+
attribute: key,
|
124
|
+
identifier: value || key
|
125
|
+
}
|
126
|
+
});
|
127
|
+
entry.dispatchEvent(attrRequest);
|
128
|
+
}
|
129
|
+
}
|
130
|
+
for (const property of Object.keys(bindingConfig)) {
|
131
|
+
if (entry.hasAttribute(`@bind:${property}`)) {
|
132
|
+
const identifier = entry.getAttribute(`@bind:${property}`)?.trim() || property;
|
133
|
+
const bindRequest = new CustomEvent("@bind-request", {
|
134
|
+
bubbles: true,
|
135
|
+
cancelable: true,
|
136
|
+
composed: true,
|
137
|
+
detail: {
|
138
|
+
property,
|
139
|
+
identifier
|
140
|
+
}
|
141
|
+
});
|
142
|
+
entry.dispatchEvent(bindRequest);
|
143
|
+
}
|
144
|
+
}
|
145
|
+
}
|
146
|
+
);
|
147
|
+
});
|
148
|
+
}, 100);
|
package/client/index.d.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Signal } from '
|
1
|
+
import { Signal, ReadonlySignal } from '@preact/signals-core';
|
2
2
|
|
3
3
|
interface AutonomousCustomElement {
|
4
4
|
/**
|
@@ -30,7 +30,6 @@ interface AutonomousCustomElement {
|
|
30
30
|
formStateRestoreCallback?(): void;
|
31
31
|
}
|
32
32
|
|
33
|
-
type ReactivityOptions = { deep: boolean };
|
34
33
|
type Destructor = () => void;
|
35
34
|
type EffectCallback = () => Destructor | void;
|
36
35
|
type EffectOptions = {
|
@@ -57,33 +56,50 @@ declare class HandlerRegistry extends HTMLElement implements AutonomousCustomEle
|
|
57
56
|
*
|
58
57
|
* An optional AbortSignal can be provided to abort the effect prematurely
|
59
58
|
*/
|
60
|
-
|
59
|
+
effect(callback: EffectCallback): void;
|
60
|
+
/**
|
61
|
+
* Looks up an identifier on the instance
|
62
|
+
*/
|
63
|
+
lookup(identifier: string): any;
|
61
64
|
connectedCallback(): void;
|
62
65
|
disconnectedCallback(): void;
|
63
66
|
}
|
64
67
|
|
65
|
-
declare
|
66
|
-
private get;
|
67
|
-
private set;
|
68
|
-
get value(): T;
|
69
|
-
set value(newValue: T);
|
70
|
-
}
|
71
|
-
declare class ReactiveComputation<T> extends Signal.Computed<T> {
|
72
|
-
private get;
|
73
|
-
get value(): T;
|
74
|
-
}
|
75
|
-
declare const $object: <T extends Record<PropertyKey, any>>(init: T, options?: ReactivityOptions) => T;
|
76
|
-
declare const $array: <T extends ArrayLike<any>>(init: T, options?: ReactivityOptions) => T;
|
77
|
-
declare const isState: (s: unknown) => s is InstanceType<typeof ReactiveValue>;
|
78
|
-
declare const isComputed: (s: unknown) => s is InstanceType<typeof ReactiveComputation>;
|
79
|
-
declare const getValue: (signal: unknown) => unknown;
|
80
|
-
declare const $state: <T>(initialValue: T, options?: Signal.Options<T | undefined>) => ReactiveValue<T>;
|
81
|
-
declare const $computed: <T>(computation: () => T, options?: Signal.Options<T>) => ReactiveComputation<T>;
|
68
|
+
declare const isState: (value: unknown) => value is InstanceType<typeof Signal>;
|
82
69
|
/**
|
83
|
-
*
|
70
|
+
* Creates a new signal
|
84
71
|
*
|
85
|
-
*
|
72
|
+
* @param value The initial value
|
73
|
+
*/
|
74
|
+
declare const signal: <T>(value: T) => Signal<T>;
|
75
|
+
/**
|
76
|
+
* Creates a read-only computed signal based on the values of other signals
|
77
|
+
*
|
78
|
+
* The computed value is updated when a tracked dependency changes
|
79
|
+
*
|
80
|
+
* @param computation The callback function doing the computation
|
81
|
+
*/
|
82
|
+
declare const computed: <T>(computation: () => T) => ReadonlySignal<T>;
|
83
|
+
/**
|
84
|
+
* Creates an unowned effect that must be cleaned up manually and runs arbitrary code in response to signals change.
|
85
|
+
*
|
86
|
+
* @param cb The effect callback to run when a tracked dependency changes. Can return a cleanup function, executed when the effect is re-run or is disposed of.
|
87
|
+
*
|
88
|
+
* @param options An AbortSignal can be passed to abort the effect
|
89
|
+
*
|
90
|
+
* @return a function to unsubscribe and cleanup the effect
|
91
|
+
*/
|
92
|
+
declare const effect: (cb: EffectCallback, options?: EffectOptions) => Destructor;
|
93
|
+
/**
|
94
|
+
* Creates a deeply reactive proxied object or array
|
95
|
+
*
|
96
|
+
* @example const obj = reactive({ a: { b: { c: 1 } } });
|
97
|
+
const computation = computed(() => obj.a.b.c * 2});
|
98
|
+
|
99
|
+
assertEquals(computation.value, 2);
|
100
|
+
obj.a.b.c = 2;
|
101
|
+
assertEquals(computation.value, 4);
|
86
102
|
*/
|
87
|
-
declare const
|
103
|
+
declare const reactive: <T>(thing: T) => T;
|
88
104
|
|
89
|
-
export {
|
105
|
+
export { HandlerRegistry, computed, effect, isState, reactive, signal };
|
package/client/index.js
CHANGED
@@ -1,6 +1,32 @@
|
|
1
|
-
import { Signal } from '
|
1
|
+
import { effect as effect$1, Signal, signal as signal$1, computed as computed$1 } from '@preact/signals-core';
|
2
2
|
|
3
3
|
// src/utils.ts
|
4
|
+
function type(value) {
|
5
|
+
if (value === null) {
|
6
|
+
return "null";
|
7
|
+
}
|
8
|
+
if (value === void 0) {
|
9
|
+
return "undefined";
|
10
|
+
}
|
11
|
+
const baseType = typeof value;
|
12
|
+
if (!["object", "function"].includes(baseType)) {
|
13
|
+
return baseType;
|
14
|
+
}
|
15
|
+
if (typeof value === "object" && Symbol.toStringTag in value) {
|
16
|
+
const tag = value[Symbol.toStringTag];
|
17
|
+
if (typeof tag === "string") {
|
18
|
+
return tag;
|
19
|
+
}
|
20
|
+
}
|
21
|
+
if (baseType === "function" && Function.prototype.toString.call(value).startsWith("class")) {
|
22
|
+
return "class";
|
23
|
+
}
|
24
|
+
const className = value.constructor.name;
|
25
|
+
if (typeof className === "string" && className !== "") {
|
26
|
+
return className.toLowerCase();
|
27
|
+
}
|
28
|
+
return baseType;
|
29
|
+
}
|
4
30
|
var booleanAttributes = [
|
5
31
|
"allowfullscreen",
|
6
32
|
// on <iframe>
|
@@ -51,8 +77,6 @@ var booleanAttributes = [
|
|
51
77
|
"selected"
|
52
78
|
// on <option>
|
53
79
|
];
|
54
|
-
|
55
|
-
// src/config.ts
|
56
80
|
var bindingConfig = {
|
57
81
|
"checked": {
|
58
82
|
element: ["input"],
|
@@ -65,126 +89,47 @@ var bindingConfig = {
|
|
65
89
|
event: "input"
|
66
90
|
}
|
67
91
|
};
|
68
|
-
var
|
69
|
-
|
70
|
-
get;
|
71
|
-
// @ts-ignore see above
|
72
|
-
set;
|
73
|
-
get value() {
|
74
|
-
return super.get();
|
75
|
-
}
|
76
|
-
set value(newValue) {
|
77
|
-
super.set(newValue);
|
78
|
-
}
|
92
|
+
var isState = (value) => {
|
93
|
+
return value instanceof Signal;
|
79
94
|
};
|
80
|
-
var
|
81
|
-
|
82
|
-
get;
|
83
|
-
get value() {
|
84
|
-
return super.get();
|
85
|
-
}
|
95
|
+
var signal = (value) => {
|
96
|
+
return signal$1(value);
|
86
97
|
};
|
87
|
-
var
|
88
|
-
|
89
|
-
if (Array.isArray(thing)) {
|
90
|
-
return $array(thing, options);
|
91
|
-
} else if (thing) {
|
92
|
-
return $object(thing, options);
|
93
|
-
}
|
94
|
-
}
|
95
|
-
return thing;
|
98
|
+
var computed = (computation) => {
|
99
|
+
return computed$1(computation);
|
96
100
|
};
|
97
|
-
var
|
98
|
-
if (options.
|
99
|
-
|
100
|
-
|
101
|
-
|
101
|
+
var effect = (cb, options) => {
|
102
|
+
if (options?.signal.aborted) return () => {
|
103
|
+
};
|
104
|
+
const dispose = effect$1(cb);
|
105
|
+
options?.signal.addEventListener("abort", dispose);
|
106
|
+
return dispose;
|
107
|
+
};
|
108
|
+
var reactive = (thing) => {
|
109
|
+
if (type(thing) === "object" || type(thing) === "array") {
|
110
|
+
return object(thing);
|
102
111
|
}
|
103
|
-
|
104
|
-
const proxy = new Proxy(init, {
|
105
|
-
get(_target, p, _receiver) {
|
106
|
-
return state.get()[p];
|
107
|
-
},
|
108
|
-
set(_target, p, newValue, _receiver) {
|
109
|
-
state.set({
|
110
|
-
...state.get(),
|
111
|
-
[p]: maybeReactiveObjectType(newValue, options)
|
112
|
-
});
|
113
|
-
return true;
|
114
|
-
}
|
115
|
-
});
|
116
|
-
return proxy;
|
112
|
+
return thing;
|
117
113
|
};
|
118
|
-
var
|
119
|
-
|
120
|
-
|
121
|
-
init[key] = maybeReactiveObjectType(value, options);
|
122
|
-
}
|
114
|
+
var object = (init) => {
|
115
|
+
for (const [key, value] of Object.entries(init)) {
|
116
|
+
init[key] = reactive(value);
|
123
117
|
}
|
124
|
-
const state =
|
118
|
+
const state = signal(init);
|
125
119
|
const proxy = new Proxy(init, {
|
126
120
|
get(_target, p, _receiver) {
|
127
|
-
return state.
|
121
|
+
return state.value[p];
|
128
122
|
},
|
129
123
|
set(_target, p, newValue, _receiver) {
|
130
|
-
state.
|
131
|
-
...state.
|
132
|
-
[p]:
|
133
|
-
}
|
124
|
+
state.value = {
|
125
|
+
...state.value,
|
126
|
+
[p]: reactive(newValue)
|
127
|
+
};
|
134
128
|
return true;
|
135
129
|
}
|
136
130
|
});
|
137
131
|
return proxy;
|
138
132
|
};
|
139
|
-
var isState = (s) => {
|
140
|
-
return Signal.isState(s);
|
141
|
-
};
|
142
|
-
var isComputed = (s) => {
|
143
|
-
return Signal.isComputed(s);
|
144
|
-
};
|
145
|
-
var getValue = (signal) => {
|
146
|
-
if (isState(signal) || isComputed(signal)) {
|
147
|
-
return signal.value;
|
148
|
-
}
|
149
|
-
return signal;
|
150
|
-
};
|
151
|
-
var $state = (initialValue, options) => {
|
152
|
-
return new ReactiveValue(initialValue, options);
|
153
|
-
};
|
154
|
-
var $computed = (computation, options) => {
|
155
|
-
return new ReactiveComputation(computation, options);
|
156
|
-
};
|
157
|
-
var pending = false;
|
158
|
-
var watcher = new Signal.subtle.Watcher(() => {
|
159
|
-
if (!pending) {
|
160
|
-
pending = true;
|
161
|
-
queueMicrotask(() => {
|
162
|
-
pending = false;
|
163
|
-
for (const s of watcher.getPending()) s.get();
|
164
|
-
watcher.watch();
|
165
|
-
});
|
166
|
-
}
|
167
|
-
});
|
168
|
-
var $effect = (cb, options) => {
|
169
|
-
if (options?.signal?.aborted) return () => {
|
170
|
-
};
|
171
|
-
let destroy;
|
172
|
-
const c = new Signal.Computed(() => {
|
173
|
-
destroy?.();
|
174
|
-
destroy = cb() ?? void 0;
|
175
|
-
});
|
176
|
-
watcher.watch(c);
|
177
|
-
c.get();
|
178
|
-
let cleaned = false;
|
179
|
-
const cleanup = () => {
|
180
|
-
if (cleaned) return;
|
181
|
-
destroy?.();
|
182
|
-
watcher.unwatch(c);
|
183
|
-
cleaned = true;
|
184
|
-
};
|
185
|
-
options?.signal.addEventListener("abort", cleanup);
|
186
|
-
return cleanup;
|
187
|
-
};
|
188
133
|
|
189
134
|
// src/handler-registry.ts
|
190
135
|
var HandlerRegistry = class extends HTMLElement {
|
@@ -204,19 +149,20 @@ var HandlerRegistry = class extends HTMLElement {
|
|
204
149
|
*
|
205
150
|
* An optional AbortSignal can be provided to abort the effect prematurely
|
206
151
|
*/
|
207
|
-
|
208
|
-
|
209
|
-
if (options?.signal) signals.push(options.signal);
|
210
|
-
$effect(callback, { ...options, signal: AbortSignal.any(signals) });
|
152
|
+
effect(callback) {
|
153
|
+
effect(callback, { signal: this.abortController.signal });
|
211
154
|
}
|
212
|
-
|
155
|
+
/**
|
156
|
+
* Looks up an identifier on the instance
|
157
|
+
*/
|
158
|
+
lookup(identifier) {
|
213
159
|
return this[identifier];
|
214
160
|
}
|
215
161
|
#handleOn(e) {
|
216
162
|
if (e instanceof CustomEvent) {
|
217
|
-
const { handler, type } = e.detail;
|
218
|
-
if (handler in this && typeof this
|
219
|
-
e.target?.addEventListener(
|
163
|
+
const { handler, type: type2 } = e.detail;
|
164
|
+
if (handler in this && typeof this.lookup(handler) === "function") {
|
165
|
+
e.target?.addEventListener(type2, this.lookup(handler).bind(this));
|
220
166
|
e.stopPropagation();
|
221
167
|
}
|
222
168
|
}
|
@@ -226,11 +172,11 @@ var HandlerRegistry = class extends HTMLElement {
|
|
226
172
|
if (e instanceof CustomEvent && target) {
|
227
173
|
const { identifier } = e.detail;
|
228
174
|
if (identifier in this) {
|
229
|
-
this
|
230
|
-
const classList =
|
175
|
+
this.effect(() => {
|
176
|
+
const classList = this.lookup(identifier)?.valueOf();
|
231
177
|
if (classList && typeof classList === "object") {
|
232
178
|
for (const [k, v] of Object.entries(classList)) {
|
233
|
-
const force = !!
|
179
|
+
const force = !!v?.valueOf();
|
234
180
|
for (const className of k.split(" ")) {
|
235
181
|
target.classList.toggle(
|
236
182
|
className,
|
@@ -247,8 +193,8 @@ var HandlerRegistry = class extends HTMLElement {
|
|
247
193
|
#handleUse(e) {
|
248
194
|
if (e instanceof CustomEvent) {
|
249
195
|
const { hook } = e.detail;
|
250
|
-
if (hook in this && typeof this
|
251
|
-
const cleanup = this
|
196
|
+
if (hook in this && typeof this.lookup(hook) === "function") {
|
197
|
+
const cleanup = this.lookup(hook).bind(this)(e.target);
|
252
198
|
if (typeof cleanup === "function") {
|
253
199
|
this.#cleanup.push(cleanup);
|
254
200
|
}
|
@@ -261,20 +207,14 @@ var HandlerRegistry = class extends HTMLElement {
|
|
261
207
|
const { identifier, attribute } = e.detail;
|
262
208
|
const target = e.target;
|
263
209
|
if (identifier in this && target instanceof HTMLElement && attribute in target) {
|
264
|
-
const ref = this
|
265
|
-
|
266
|
-
const value = getValue(ref);
|
210
|
+
const ref = this.lookup(identifier);
|
211
|
+
this.effect(() => {
|
267
212
|
if (booleanAttributes.includes(attribute)) {
|
268
|
-
|
213
|
+
ref.valueOf() ? target.setAttribute(attribute, "") : target.removeAttribute(attribute);
|
269
214
|
} else {
|
270
|
-
target.setAttribute(attribute, `${
|
215
|
+
target.setAttribute(attribute, `${ref}`);
|
271
216
|
}
|
272
|
-
};
|
273
|
-
if (isState(ref) || isComputed(ref)) {
|
274
|
-
this.$effect(() => setAttr());
|
275
|
-
} else {
|
276
|
-
setAttr();
|
277
|
-
}
|
217
|
+
});
|
278
218
|
e.stopPropagation();
|
279
219
|
}
|
280
220
|
}
|
@@ -284,16 +224,10 @@ var HandlerRegistry = class extends HTMLElement {
|
|
284
224
|
const { identifier, property } = e.detail;
|
285
225
|
const target = e.target;
|
286
226
|
if (identifier in this && target && property in target) {
|
287
|
-
const ref = this
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
};
|
292
|
-
if (isState(ref) || isComputed(ref)) {
|
293
|
-
this.$effect(() => setProp());
|
294
|
-
} else {
|
295
|
-
setProp();
|
296
|
-
}
|
227
|
+
const ref = this.lookup(identifier);
|
228
|
+
this.effect(() => {
|
229
|
+
target[property] = ref.valueOf();
|
230
|
+
});
|
297
231
|
e.stopPropagation();
|
298
232
|
}
|
299
233
|
}
|
@@ -303,16 +237,10 @@ var HandlerRegistry = class extends HTMLElement {
|
|
303
237
|
const target = e.target;
|
304
238
|
const { identifier } = e.detail;
|
305
239
|
if (identifier in this && target instanceof HTMLElement) {
|
306
|
-
const ref = this
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
};
|
311
|
-
if (isState(ref) || isComputed(ref)) {
|
312
|
-
this.$effect(() => setTextContent());
|
313
|
-
} else {
|
314
|
-
setTextContent();
|
315
|
-
}
|
240
|
+
const ref = this.lookup(identifier);
|
241
|
+
this.effect(() => {
|
242
|
+
target.textContent = `${ref}`;
|
243
|
+
});
|
316
244
|
e.stopPropagation();
|
317
245
|
}
|
318
246
|
}
|
@@ -322,16 +250,10 @@ var HandlerRegistry = class extends HTMLElement {
|
|
322
250
|
const { identifier } = e.detail;
|
323
251
|
const target = e.target;
|
324
252
|
if (identifier in this && target instanceof HTMLElement) {
|
325
|
-
const ref = this
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
};
|
330
|
-
if (isState(ref) || isComputed(ref)) {
|
331
|
-
this.$effect(() => setInnerHTML());
|
332
|
-
} else {
|
333
|
-
setInnerHTML();
|
334
|
-
}
|
253
|
+
const ref = this.lookup(identifier);
|
254
|
+
this.effect(() => {
|
255
|
+
target.innerHTML = `${ref}`;
|
256
|
+
});
|
335
257
|
e.stopPropagation();
|
336
258
|
}
|
337
259
|
}
|
@@ -341,13 +263,13 @@ var HandlerRegistry = class extends HTMLElement {
|
|
341
263
|
const { identifier, property } = e.detail;
|
342
264
|
const target = e.target;
|
343
265
|
if (identifier in this && target instanceof HTMLElement && property in target) {
|
344
|
-
const state = this
|
266
|
+
const state = this.lookup(identifier);
|
345
267
|
if (isState(state)) {
|
346
268
|
state.value = target[property];
|
347
269
|
target.addEventListener(bindingConfig[property].event, () => {
|
348
270
|
state.value = target[property];
|
349
271
|
});
|
350
|
-
this
|
272
|
+
this.effect(() => {
|
351
273
|
target[property] = state.value;
|
352
274
|
});
|
353
275
|
}
|
@@ -356,15 +278,15 @@ var HandlerRegistry = class extends HTMLElement {
|
|
356
278
|
}
|
357
279
|
}
|
358
280
|
connectedCallback() {
|
359
|
-
const { signal } = this.abortController;
|
360
|
-
this.addEventListener("@attr-request", this.#handleAttr, { signal });
|
361
|
-
this.addEventListener("@class-request", this.#handleClass, { signal });
|
362
|
-
this.addEventListener("@on-request", this.#handleOn, { signal });
|
363
|
-
this.addEventListener("@use-request", this.#handleUse, { signal });
|
364
|
-
this.addEventListener("@prop-request", this.#handleProp, { signal });
|
365
|
-
this.addEventListener("@html-request", this.#handleHTML, { signal });
|
366
|
-
this.addEventListener("@text-request", this.#handleText, { signal });
|
367
|
-
this.addEventListener("@bind-request", this.#handleBind, { signal });
|
281
|
+
const { signal: signal2 } = this.abortController;
|
282
|
+
this.addEventListener("@attr-request", this.#handleAttr, { signal: signal2 });
|
283
|
+
this.addEventListener("@class-request", this.#handleClass, { signal: signal2 });
|
284
|
+
this.addEventListener("@on-request", this.#handleOn, { signal: signal2 });
|
285
|
+
this.addEventListener("@use-request", this.#handleUse, { signal: signal2 });
|
286
|
+
this.addEventListener("@prop-request", this.#handleProp, { signal: signal2 });
|
287
|
+
this.addEventListener("@html-request", this.#handleHTML, { signal: signal2 });
|
288
|
+
this.addEventListener("@text-request", this.#handleText, { signal: signal2 });
|
289
|
+
this.addEventListener("@bind-request", this.#handleBind, { signal: signal2 });
|
368
290
|
}
|
369
291
|
disconnectedCallback() {
|
370
292
|
this.abortController.abort();
|
@@ -375,4 +297,4 @@ var HandlerRegistry = class extends HTMLElement {
|
|
375
297
|
};
|
376
298
|
customElements?.define("handler-registry", HandlerRegistry);
|
377
299
|
|
378
|
-
export {
|
300
|
+
export { HandlerRegistry, computed, effect, isState, reactive, signal };
|
@@ -0,0 +1,30 @@
|
|
1
|
+
declare const spaces_sep_by_comma: RegExp;
|
2
|
+
/**
|
3
|
+
* Idempotent string conversion to kebab-case
|
4
|
+
*/
|
5
|
+
declare const toKebabCase: (str: string) => string;
|
6
|
+
/**
|
7
|
+
* Idempotent string conversion to PascalCase
|
8
|
+
*/
|
9
|
+
declare const toPascalCase: (str: string) => string;
|
10
|
+
type LooseAutocomplete<T extends string> = T | Omit<string, T>;
|
11
|
+
type Types = LooseAutocomplete<"null" | "undefined" | "boolean" | "number" | "bigint" | "string" | "symbol" | "function" | "class" | "array" | "date" | "error" | "regexp" | "object">;
|
12
|
+
/**
|
13
|
+
* A more reliable `typeof` function
|
14
|
+
*/
|
15
|
+
declare function type(value: unknown): Types;
|
16
|
+
declare const booleanAttributes: string[];
|
17
|
+
declare const bindingConfig: {
|
18
|
+
checked: {
|
19
|
+
element: string[];
|
20
|
+
type: string[];
|
21
|
+
event: string;
|
22
|
+
};
|
23
|
+
value: {
|
24
|
+
element: string[];
|
25
|
+
type: string[];
|
26
|
+
event: string;
|
27
|
+
};
|
28
|
+
};
|
29
|
+
|
30
|
+
export { bindingConfig, booleanAttributes, spaces_sep_by_comma, toKebabCase, toPascalCase, type };
|
package/client/utils.js
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
// src/utils.ts
|
2
|
+
var is_upper = /[A-Z]/;
|
3
|
+
var spaces_sep_by_comma = /\s*,\s*/;
|
4
|
+
var toKebabCase = (str) => {
|
5
|
+
let kebab = "";
|
6
|
+
for (let index = 0; index < str.length; index++) {
|
7
|
+
const char = str[index];
|
8
|
+
if (index !== 0 && is_upper.test(char)) {
|
9
|
+
kebab += `-${char.toLowerCase()}`;
|
10
|
+
} else {
|
11
|
+
kebab += char.toLowerCase();
|
12
|
+
}
|
13
|
+
}
|
14
|
+
return kebab;
|
15
|
+
};
|
16
|
+
var toPascalCase = (str) => {
|
17
|
+
let pascal = "";
|
18
|
+
let toUpper = true;
|
19
|
+
for (let index = 0; index < str.length; index++) {
|
20
|
+
const char = str[index];
|
21
|
+
if (char === "-") {
|
22
|
+
toUpper = true;
|
23
|
+
continue;
|
24
|
+
}
|
25
|
+
if (toUpper) {
|
26
|
+
pascal += char.toUpperCase();
|
27
|
+
toUpper = false;
|
28
|
+
} else {
|
29
|
+
pascal += char.toLowerCase();
|
30
|
+
}
|
31
|
+
}
|
32
|
+
return pascal;
|
33
|
+
};
|
34
|
+
function type(value) {
|
35
|
+
if (value === null) {
|
36
|
+
return "null";
|
37
|
+
}
|
38
|
+
if (value === void 0) {
|
39
|
+
return "undefined";
|
40
|
+
}
|
41
|
+
const baseType = typeof value;
|
42
|
+
if (!["object", "function"].includes(baseType)) {
|
43
|
+
return baseType;
|
44
|
+
}
|
45
|
+
if (typeof value === "object" && Symbol.toStringTag in value) {
|
46
|
+
const tag = value[Symbol.toStringTag];
|
47
|
+
if (typeof tag === "string") {
|
48
|
+
return tag;
|
49
|
+
}
|
50
|
+
}
|
51
|
+
if (baseType === "function" && Function.prototype.toString.call(value).startsWith("class")) {
|
52
|
+
return "class";
|
53
|
+
}
|
54
|
+
const className = value.constructor.name;
|
55
|
+
if (typeof className === "string" && className !== "") {
|
56
|
+
return className.toLowerCase();
|
57
|
+
}
|
58
|
+
return baseType;
|
59
|
+
}
|
60
|
+
var booleanAttributes = [
|
61
|
+
"allowfullscreen",
|
62
|
+
// on <iframe>
|
63
|
+
"async",
|
64
|
+
// on <script>
|
65
|
+
"autofocus",
|
66
|
+
// on <button>, <input>, <select>, <textarea>
|
67
|
+
"autoplay",
|
68
|
+
// on <audio>, <video>
|
69
|
+
"checked",
|
70
|
+
// on <input type="checkbox">, <input type="radio">
|
71
|
+
"controls",
|
72
|
+
// on <audio>, <video>
|
73
|
+
"default",
|
74
|
+
// on <track>
|
75
|
+
"defer",
|
76
|
+
// on <script>
|
77
|
+
"disabled",
|
78
|
+
// on form elements like <button>, <fieldset>, <input>, <optgroup>, <option>,<select>, <textarea>
|
79
|
+
"formnovalidate",
|
80
|
+
// on <button>, <input type="submit">
|
81
|
+
"hidden",
|
82
|
+
// global
|
83
|
+
"inert",
|
84
|
+
// global
|
85
|
+
"ismap",
|
86
|
+
// on <img>
|
87
|
+
"itemscope",
|
88
|
+
// global; part of microdata
|
89
|
+
"loop",
|
90
|
+
// on <audio>, <video>
|
91
|
+
"multiple",
|
92
|
+
// on <input type="file">, <select>
|
93
|
+
"muted",
|
94
|
+
// on <audio>, <video>
|
95
|
+
"nomodule",
|
96
|
+
// on <script>
|
97
|
+
"novalidate",
|
98
|
+
// on <form>
|
99
|
+
"open",
|
100
|
+
// on <details>
|
101
|
+
"readonly",
|
102
|
+
// on <input>, <textarea>
|
103
|
+
"required",
|
104
|
+
// on <input>, <select>, <textarea>
|
105
|
+
"reversed",
|
106
|
+
// on <ol>
|
107
|
+
"selected"
|
108
|
+
// on <option>
|
109
|
+
];
|
110
|
+
var bindingConfig = {
|
111
|
+
"checked": {
|
112
|
+
element: ["input"],
|
113
|
+
type: ["boolean"],
|
114
|
+
event: "change"
|
115
|
+
},
|
116
|
+
"value": {
|
117
|
+
element: ["input", "select", "textarea"],
|
118
|
+
type: ["string", "number"],
|
119
|
+
event: "input"
|
120
|
+
}
|
121
|
+
};
|
122
|
+
|
123
|
+
export { bindingConfig, booleanAttributes, spaces_sep_by_comma, toKebabCase, toPascalCase, type };
|
package/package.json
CHANGED
@@ -1,9 +1,16 @@
|
|
1
1
|
{
|
2
2
|
"name": "@radishland/runtime",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.1.1",
|
4
4
|
"type": "module",
|
5
5
|
"description": "The Radish runtime",
|
6
6
|
"author": "Frédéric Crozatier",
|
7
|
+
"repository": {
|
8
|
+
"type": "git",
|
9
|
+
"url": "git+https://github.com/radishland/radish.git"
|
10
|
+
},
|
11
|
+
"bugs": {
|
12
|
+
"url": "https://github.com/radishland/radish/issues"
|
13
|
+
},
|
7
14
|
"license": "MIT",
|
8
15
|
"scripts": {
|
9
16
|
"build": "tsup",
|
@@ -13,6 +20,14 @@
|
|
13
20
|
".": {
|
14
21
|
"import": "./client/index.js",
|
15
22
|
"types": "./client/index.d.ts"
|
23
|
+
},
|
24
|
+
"./boot": {
|
25
|
+
"import": "./client/boot.js",
|
26
|
+
"types": "./client/boot.d.ts"
|
27
|
+
},
|
28
|
+
"./utils": {
|
29
|
+
"import": "./client/utils.js",
|
30
|
+
"types": "./client/utils.d.ts"
|
16
31
|
}
|
17
32
|
},
|
18
33
|
"files": [
|
@@ -21,8 +36,12 @@
|
|
21
36
|
"LICENCE",
|
22
37
|
"package.json"
|
23
38
|
],
|
39
|
+
"keywords": [
|
40
|
+
"radish",
|
41
|
+
"runtime"
|
42
|
+
],
|
24
43
|
"dependencies": {
|
25
|
-
"
|
44
|
+
"@preact/signals-core": "^1.8.0"
|
26
45
|
},
|
27
46
|
"devDependencies": {
|
28
47
|
"tsup": "^8.4.0",
|