riftt 1.0.0 → 1.0.2

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.
@@ -1,100 +0,0 @@
1
- import sprae, { parse } from "../core.js";
2
- import { dir } from "../core.js";
3
- import { setter } from "../store.js";
4
- import { attr } from "./default.js";
5
-
6
- dir("value", (el, state, expr) => {
7
- const type = el.type || "";
8
- const tag = el.tagName;
9
-
10
- const setText = (value) => {
11
- const v = value ?? "";
12
- el.value = v;
13
- el.setAttribute("value", v);
14
- };
15
-
16
- const textUpdate = (value) => setText(value);
17
-
18
- const textareaUpdate = (value) => {
19
- const from = el.selectionStart;
20
- const to = el.selectionEnd;
21
- setText(value);
22
- if (from) el.setSelectionRange(from, to);
23
- };
24
-
25
- const checkboxUpdate = (value) => {
26
- el.checked = value;
27
- attr(el, "checked", value);
28
- };
29
-
30
- const radioUpdate = (value) => {
31
- if (el.value === value) {
32
- el.checked = value;
33
- attr(el, "checked", value);
34
- }
35
- };
36
-
37
- const selectOneUpdate = (value) => {
38
- for (const o of el.options) {
39
- if (o.value == value) o.setAttribute("selected", "");
40
- else o.removeAttribute("selected");
41
- }
42
- el.value = value;
43
- };
44
-
45
- const selectMultipleUpdate = (value) => {
46
- for (const o of el.options) o.removeAttribute("selected");
47
- for (const v of value) el.querySelector(`[value="${v}"]`)?.setAttribute("selected", "");
48
- };
49
-
50
- const defaultUpdate = (value) => {
51
- el.value = value;
52
- };
53
-
54
- const pickUpdater = () => {
55
- if (tag === "TEXTAREA") return textareaUpdate;
56
- switch (type) {
57
- case "text":
58
- case "":
59
- return textUpdate;
60
- case "checkbox":
61
- return checkboxUpdate;
62
- case "radio":
63
- return radioUpdate;
64
- case "select-one":
65
- return selectOneUpdate;
66
- case "select-multiple":
67
- return selectMultipleUpdate;
68
- default:
69
- return defaultUpdate;
70
- }
71
- };
72
-
73
- const update = pickUpdater();
74
-
75
- try {
76
- const set = setter(expr);
77
-
78
- const handleChange =
79
- type === "checkbox"
80
- ? () => set(state, el.checked)
81
- : type === "select-multiple"
82
- ? () =>
83
- set(
84
- state,
85
- [...el.selectedOptions].map((o) => o.value),
86
- )
87
- : () => set(state, el.selectedIndex < 0 ? null : el.value);
88
-
89
- el.oninput = el.onchange = handleChange;
90
-
91
- if (type?.startsWith("select")) {
92
- new MutationObserver(handleChange).observe(el, { childList: true, subtree: true, attributes: true });
93
- sprae(el, state);
94
- }
95
-
96
- parse(expr)(state) ?? handleChange();
97
- } catch {}
98
-
99
- return update;
100
- });
@@ -1,20 +0,0 @@
1
- import sprae, { dir } from "../core.js";
2
- import { untracked } from "../signal.js";
3
- import store from "../store.js";
4
-
5
- dir("with", (el, rootState) => {
6
- let local;
7
-
8
- return (values) => {
9
- if (!local) {
10
- // force untracked to avoid accidental subscriptions during setup
11
- untracked(() => {
12
- local = store(values, rootState);
13
- sprae(el, local);
14
- });
15
- return;
16
- }
17
-
18
- sprae(el, values);
19
- };
20
- });
@@ -1,214 +0,0 @@
1
- import { effect } from "./signal.js";
2
- import { _signals, _change } from "./store.js";
3
-
4
- /**
5
- * symbol for accessing persistence configuration on a store
6
- * @type {symbol}
7
- */
8
- export const _persist = Symbol("persist");
9
-
10
- /**
11
- * persistence configuration options
12
- * @typedef {Object} PersistOptions
13
- * @property {string} [key] - localStorage key (defaults to store name or auto-generated)
14
- * @property {Storage} [storage=localStorage] - storage interface (localStorage, sessionStorage, or custom)
15
- * @property {string[]} [paths] - specific paths to persist (defaults to all)
16
- * @property {number} [debounce=300] - debounce delay in milliseconds for writes
17
- * @property {function} [serializer] - custom serializer for complex types
18
- * @property {function} [deserializer] - custom deserializer for complex types
19
- */
20
-
21
- /**
22
- * default serializer that handles common types
23
- * @param {any} value - value to serialize
24
- * @returns {string} serialized value
25
- */
26
- const defaultSerializer = (value) => {
27
- try {
28
- return JSON.stringify(value, (key, val) => {
29
- if (typeof val === "function") return undefined;
30
- if (val && typeof val === "object" && val.constructor !== Object && !Array.isArray(val)) {
31
- return undefined;
32
- }
33
- return val;
34
- });
35
- } catch {
36
- return "{}";
37
- }
38
- };
39
-
40
- /**
41
- * default deserializer
42
- * @param {string} value - serialized value
43
- * @returns {any} deserialized value
44
- */
45
- const defaultDeserializer = (value) => {
46
- try {
47
- return JSON.parse(value);
48
- } catch {
49
- return {};
50
- }
51
- };
52
-
53
- /**
54
- * creates a debounced function that delays execution
55
- * @param {function} fn - function to debounce
56
- * @param {number} delay - delay in milliseconds
57
- * @returns {function} debounced function
58
- */
59
- const debounce = (fn, delay) => {
60
- let timeoutId;
61
- return (...args) => {
62
- clearTimeout(timeoutId);
63
- timeoutId = setTimeout(() => fn(...args), delay);
64
- };
65
- };
66
-
67
- /**
68
- * extracts values from store at specified paths
69
- * @param {Object} store - the store proxy
70
- * @param {string[]} paths - paths to extract
71
- * @returns {Object} extracted values
72
- */
73
- const extractPaths = (store, paths) => {
74
- const result = {};
75
-
76
- if (!paths) {
77
- const signals = store[_signals];
78
- for (const key in signals) {
79
- const signal = signals[key];
80
- if (signal && !signal._set && key[0] !== "_") {
81
- result[key] = store[key];
82
- }
83
- }
84
- } else {
85
- for (const path of paths) {
86
- if (path in store) {
87
- result[path] = store[path];
88
- }
89
- }
90
- }
91
-
92
- return result;
93
- };
94
-
95
- /**
96
- * checks if storage is available
97
- * @param {Storage} storage - storage to test
98
- * @returns {boolean} whether storage is available
99
- */
100
- const isStorageAvailable = (storage) => {
101
- try {
102
- const testKey = "__storage_test__";
103
- storage.setItem(testKey, "test");
104
- storage.removeItem(testKey);
105
- return true;
106
- } catch {
107
- return false;
108
- }
109
- };
110
-
111
- /**
112
- * creates persistence functionality for a store
113
- * @param {Object} store - the store proxy
114
- * @param {string} storeKey - unique key for the store
115
- * @param {PersistOptions} options - persistence configuration
116
- * @returns {function} cleanup function to stop persistence
117
- */
118
- export const createPersistence = (store, storeKey, options = {}) => {
119
- const config = {
120
- key: storeKey,
121
- storage: globalThis.localStorage,
122
- paths: null,
123
- debounce: 0,
124
- serializer: defaultSerializer,
125
- deserializer: defaultDeserializer,
126
- ...options,
127
- };
128
-
129
- if (!isStorageAvailable(config.storage)) {
130
- console.warn(`persistence: ${config.storage === globalThis.localStorage ? "localStorage" : "storage"} is not available`);
131
- return () => {};
132
- }
133
-
134
- let isHydrating = false;
135
-
136
- /**
137
- * saves current store state to storage
138
- */
139
- const saveState = debounce(() => {
140
- if (isHydrating) return;
141
-
142
- try {
143
- const state = extractPaths(store, config.paths);
144
- const serialized = config.serializer(state);
145
- config.storage.setItem(config.key, serialized);
146
- } catch (error) {
147
- console.warn("persistence: failed to save state", error);
148
- }
149
- }, config.debounce);
150
-
151
- /**
152
- * restores state from storage
153
- */
154
- const restoreState = () => {
155
- try {
156
- const stored = config.storage.getItem(config.key);
157
- if (!stored) return;
158
-
159
- const state = config.deserializer(stored);
160
- if (!state || typeof state !== "object") return;
161
-
162
- isHydrating = true;
163
-
164
- for (const key in state) {
165
- if (key in store[_signals] && !store[_signals][key]?._set) {
166
- store[key] = state[key];
167
- }
168
- }
169
-
170
- isHydrating = false;
171
- } catch (error) {
172
- console.warn("persistence: failed to restore state", error);
173
- isHydrating = false;
174
- }
175
- };
176
-
177
- restoreState();
178
-
179
- const cleanup = effect(() => {
180
- if (config.paths) {
181
- for (const path of config.paths) {
182
- store[path];
183
- }
184
- } else {
185
- const signals = store[_signals];
186
- for (const key in signals) {
187
- const signal = signals[key];
188
- if (signal && !signal._set && key[0] !== "_") {
189
- store[key];
190
- }
191
- }
192
- }
193
- saveState();
194
- });
195
-
196
- store[_persist] = { config, saveState, restoreState };
197
-
198
- return cleanup;
199
- };
200
-
201
- /**
202
- * applies persistence to a store
203
- * @param {Object} store - the store to persist
204
- * @param {string} key - unique key for persistence
205
- * @param {PersistOptions|boolean} options - persistence options or true for defaults
206
- * @returns {function} cleanup function
207
- */
208
- export const persist = (store, key, options = {}) => {
209
- if (options === true) {
210
- options = {};
211
- }
212
-
213
- return createPersistence(store, key, options);
214
- };
@@ -1,106 +0,0 @@
1
- let current;
2
-
3
- export let signal = (initialValue) => {
4
- let value = initialValue;
5
- const observers = new Set();
6
-
7
- const signalObject = {
8
- get value() {
9
- if (current) {
10
- current.deps.push(observers.add(current));
11
- }
12
- return value;
13
- },
14
-
15
- set value(newValue) {
16
- if (newValue === value) return;
17
- value = newValue;
18
- for (const subscriber of observers) {
19
- subscriber();
20
- }
21
- },
22
-
23
- peek() {
24
- return value;
25
- },
26
- };
27
-
28
- signalObject.toJSON = () => signalObject.value;
29
- signalObject.then = () => signalObject.value;
30
- signalObject.toString = () => signalObject.value;
31
- signalObject.valueOf = () => signalObject.value;
32
-
33
- return signalObject;
34
- };
35
-
36
- export let effect = (effectFunction) => {
37
- let teardownFunction;
38
- let previousCurrent;
39
-
40
- const effectExecutor = () => {
41
- if (teardownFunction) {
42
- teardownFunction.call();
43
- }
44
-
45
- previousCurrent = current;
46
- current = effectExecutor;
47
-
48
- try {
49
- teardownFunction = effectFunction();
50
- } finally {
51
- current = previousCurrent;
52
- }
53
- };
54
-
55
- effectExecutor.deps = [];
56
- effectExecutor();
57
-
58
- return () => {
59
- if (teardownFunction) {
60
- teardownFunction.call();
61
- }
62
-
63
- let dependency;
64
- while ((dependency = effectExecutor.deps.pop())) {
65
- dependency.delete(effectExecutor);
66
- }
67
- };
68
- };
69
-
70
- export let computed = (computeFunction) => {
71
- const internalSignal = signal();
72
- let effectCleanup;
73
-
74
- const computedObject = {
75
- get value() {
76
- if (!effectCleanup) {
77
- effectCleanup = effect(() => {
78
- internalSignal.value = computeFunction();
79
- });
80
- }
81
- return internalSignal.value;
82
- },
83
-
84
- peek: internalSignal.peek,
85
- };
86
-
87
- computedObject.toJSON = () => computedObject.value;
88
- computedObject.then = () => computedObject.value;
89
- computedObject.toString = () => computedObject.value;
90
- computedObject.valueOf = () => computedObject.value;
91
-
92
- return computedObject;
93
- };
94
-
95
- export let batch = (fn) => fn();
96
-
97
- export let untracked = batch;
98
-
99
- // signals adapter - allows switching signals implementation and not depend on core
100
- export let use = (impl) => {
101
- signal = impl.signal;
102
- effect = impl.effect;
103
- computed = impl.computed;
104
- batch = impl.batch || batch;
105
- untracked = impl.untracked || untracked;
106
- };
@@ -1,20 +0,0 @@
1
- import sprae from "./core.js";
2
-
3
- // default directives
4
- import "./directive/if.js";
5
- import "./directive/each.js";
6
- import "./directive/ref.js";
7
- import "./directive/with.js";
8
- import "./directive/text.js";
9
- import "./directive/class.js";
10
- import "./directive/style.js";
11
- import "./directive/value.js";
12
- import "./directive/fx.js";
13
- import "./directive/default.js";
14
- import "./directive/aria.js";
15
- import "./directive/data.js";
16
-
17
- // default compiler (indirect new Function to avoid detector)
18
- sprae.use({ compile: (expr) => sprae.constructor(`with (arguments[0]) { return ${expr} };`) });
19
-
20
- export default sprae;
@@ -1,197 +0,0 @@
1
- // signals-based proxy
2
- import { signal, computed, batch } from "./signal.js";
3
- import { parse } from "./core.js";
4
-
5
- export const _signals = Symbol("signals");
6
- export const _change = Symbol("change");
7
- export const _stash = "__";
8
-
9
- // length changing methods
10
- const mutatingMethods = ["push", "pop", "shift", "unshift", "splice"];
11
-
12
- // set/update signal value
13
- const set = (signals, key, value) => {
14
- const existingSignal = signals[key];
15
- const currentValue = existingSignal?.peek();
16
-
17
- if (key[0] === "_") {
18
- signals[key] = value;
19
- return;
20
- }
21
-
22
- if (!existingSignal) {
23
- signals[key] = value?.peek ? value : signal(store(value));
24
- return;
25
- }
26
-
27
- if (value === currentValue) {
28
- return;
29
- }
30
-
31
- if (existingSignal._set) {
32
- existingSignal._set(value);
33
- return;
34
- }
35
-
36
- if (Array.isArray(value) && Array.isArray(currentValue)) {
37
- if (currentValue[_change]) {
38
- batch(() => {
39
- for (let i = 0; i < value.length; i++) {
40
- currentValue[i] = value[i];
41
- }
42
- currentValue.length = value.length;
43
- });
44
- } else {
45
- existingSignal.value = value;
46
- }
47
- return;
48
- }
49
-
50
- existingSignal.value = store(value);
51
- };
52
-
53
- // object store is not lazy
54
- export const store = (values, parent) => {
55
- if (!values) {
56
- return values;
57
- }
58
-
59
- if (values[_signals] || values[Symbol.toStringTag]) {
60
- return values;
61
- }
62
-
63
- if (values.constructor !== Object) {
64
- return Array.isArray(values) ? list(values) : values;
65
- }
66
-
67
- const signals = Object.create(parent?.[_signals] || {});
68
- const lengthSignal = signal(Object.keys(values).length);
69
- let stash;
70
-
71
- const state = new Proxy(signals, {
72
- get(target, key) {
73
- if (key === _change) return lengthSignal;
74
- if (key === _signals) return signals;
75
- if (key === _stash) return stash;
76
- if (key in signals) return signals[key]?.valueOf();
77
- return globalThis[key];
78
- },
79
-
80
- set(target, key, value) {
81
- if (key === _stash) {
82
- stash = value;
83
- return true;
84
- }
85
-
86
- const existed = key in signals;
87
- set(signals, key, value);
88
- if (!existed) {
89
- lengthSignal.value++;
90
- }
91
- return true;
92
- },
93
-
94
- deleteProperty(target, key) {
95
- const signalToDelete = signals[key];
96
- if (signalToDelete) {
97
- signalToDelete[Symbol.dispose]?.();
98
- delete signals[key];
99
- lengthSignal.value--;
100
- }
101
- return true;
102
- },
103
-
104
- ownKeys(target) {
105
- lengthSignal.value;
106
- return Reflect.ownKeys(signals);
107
- },
108
-
109
- has(target, key) {
110
- return true;
111
- },
112
- });
113
-
114
- const descriptors = Object.getOwnPropertyDescriptors(values);
115
-
116
- for (const key in values) {
117
- const descriptor = descriptors[key];
118
-
119
- if (descriptor?.get) {
120
- const computedSignal = computed(descriptor.get.bind(state));
121
- computedSignal._set = descriptor.set?.bind(state);
122
- signals[key] = computedSignal;
123
- } else {
124
- signals[key] = null;
125
- set(signals, key, values[key]);
126
- }
127
- }
128
-
129
- return state;
130
- };
131
-
132
- // array store - signals are lazy since arrays can be very large & expensive
133
- export const list = (values) => {
134
- let lastAccessedProperty;
135
- const lengthSignal = signal(values.length);
136
- const signals = Array(values.length).fill();
137
-
138
- const state = new Proxy(signals, {
139
- get(target, key) {
140
- if (typeof key === "symbol") {
141
- if (key === _change) return lengthSignal;
142
- if (key === _signals) return signals;
143
- return signals[key];
144
- }
145
-
146
- if (key === "length") {
147
- return mutatingMethods.includes(lastAccessedProperty) ? lengthSignal.peek() : lengthSignal.value;
148
- }
149
-
150
- lastAccessedProperty = key;
151
-
152
- if (!signals[key]) {
153
- signals[key] = signal(store(values[key]));
154
- }
155
-
156
- return signals[key].valueOf();
157
- },
158
-
159
- set(target, key, value) {
160
- if (key === "length") {
161
- for (let i = value; i < signals.length; i++) {
162
- delete state[i];
163
- }
164
- lengthSignal.value = signals.length = value;
165
- return true;
166
- }
167
-
168
- set(signals, key, value);
169
-
170
- if (key >= lengthSignal.peek()) {
171
- lengthSignal.value = signals.length = +key + 1;
172
- }
173
-
174
- return true;
175
- },
176
-
177
- deleteProperty(target, key) {
178
- signals[key]?.[Symbol.dispose]?.();
179
- delete signals[key];
180
- return true;
181
- },
182
- });
183
-
184
- return state;
185
- };
186
-
187
- // create expression setter, reflecting value back to state
188
- export const setter = (expression) => {
189
- const fn = parse(`${expression}=${_stash}`);
190
-
191
- return (state, value) => {
192
- state[_stash] = value;
193
- fn(state);
194
- };
195
- };
196
-
197
- export default store;