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.
package/src/sprae/core.js DELETED
@@ -1,220 +0,0 @@
1
- import { use, effect, untracked } from "./signal.js";
2
- import { store } from "./store.js";
3
-
4
- export const _dispose = (Symbol.dispose ||= Symbol("dispose"));
5
- export const _state = Symbol("state");
6
- export const _on = Symbol("on");
7
- export const _off = Symbol("off");
8
-
9
- export const directive = {};
10
-
11
- const memo = {};
12
-
13
- /**
14
- * Compiles an expression into an evaluator function.
15
- *
16
- * @type {(expr: string) => Function}
17
- */
18
- export let compile;
19
-
20
- /**
21
- * Attributes prefix, by default ':'
22
- */
23
- export let prefix = ":";
24
-
25
- /**
26
- * Register a directive with a parsed expression and evaluator.
27
- * @param {string} name - The name of the directive.
28
- * @param {(el: Element, state: Object, expr: string, name: string) => (value: any) => void} create - A function to create the directive.
29
- * @param {(expr: string) => (state: Object) => any} [p=parse] - Create evaluator from expression string.
30
- */
31
- export const dir = (name, create, toEvaluator = parse) => {
32
- directive[name] = (element, expression, state, directiveName) => {
33
- const update = create(element, state, expression, directiveName);
34
- const evaluate = toEvaluator(expression, ":" + directiveName);
35
- return () => update(evaluate(state));
36
- };
37
- };
38
-
39
- /**
40
- * Parses an expression into an evaluator function, caching the result for reuse.
41
- *
42
- * @param {string} expr - The expression to parse and compile into a function.
43
- * @param {string} dir - The directive associated with the expression (used for error reporting).
44
- * @returns {Function} The compiled evaluator function for the expression.
45
- */
46
- export const parse = (expression, directive) => {
47
- const key = expression.trim();
48
-
49
- if (memo[key]) {
50
- return memo[key];
51
- }
52
-
53
- let compiledFunction;
54
-
55
- try {
56
- compiledFunction = compile(key);
57
- } catch (error) {
58
- err(error, directive, key);
59
- }
60
-
61
- const evaluator = (state) => {
62
- try {
63
- return compiledFunction(state);
64
- } catch (error) {
65
- err(error, directive, key);
66
- }
67
- };
68
-
69
- memo[key] = evaluator;
70
- return evaluator;
71
- };
72
-
73
- /**
74
- * Branded sprae error with context about the directive and expression
75
- *
76
- * @param {Error} e - The original error object to enhance.
77
- * @param {string} dir - The directive where the error occurred.
78
- * @param {string} [expr=''] - The expression associated with the error, if any.
79
- * @throws {Error} The enhanced error object with a formatted message.
80
- */
81
- export const err = (error, directive = "", expression = "") => {
82
- const contextMessage = expression ? `="${expression}"\n\n` : "";
83
- const enhancedMessage = `◆ ${error.message}\n\n${directive}${contextMessage}`;
84
-
85
- throw Object.assign(error, {
86
- message: enhancedMessage,
87
- expr: expression,
88
- });
89
- };
90
-
91
- const initializeElement = (element, attributes = element.attributes, state, effects, cleanupFunctions) => {
92
- if (!attributes) return;
93
-
94
- let attributeIndex = 0;
95
-
96
- while (attributeIndex < attributes.length) {
97
- const attribute = attributes[attributeIndex];
98
- const { name: attributeName, value: attributeValue } = attribute;
99
-
100
- if (attributeName.startsWith(prefix)) {
101
- element.removeAttribute(attributeName);
102
-
103
- const directiveNames = attributeName.slice(prefix.length).split(":");
104
-
105
- for (const directiveName of directiveNames) {
106
- const directiveHandler = directive[directiveName] || directive.default;
107
- const update = directiveHandler(element, attributeValue, state, directiveName);
108
-
109
- effects.push(update);
110
- cleanupFunctions.push(effect(update));
111
-
112
- if (element[_state] === null) {
113
- return;
114
- }
115
- }
116
- } else {
117
- attributeIndex++;
118
- }
119
- }
120
-
121
- for (const childNode of element.childNodes) {
122
- if (childNode.nodeType === 1) {
123
- initializeElement(childNode, childNode.attributes, state, effects, cleanupFunctions);
124
- }
125
- }
126
- };
127
-
128
- /**
129
- * Applies directives to an HTML element and manages its reactive state.
130
- *
131
- * @param {Element} [el=document.body] - The target HTML element to apply directives to.
132
- * @param {Object} [values] - Initial values to populate the element's reactive state.
133
- * @returns {Object} The reactive state object associated with the element.
134
- */
135
- export const sprae = (element = document.body, values) => {
136
- if (element[_state]) {
137
- return Object.assign(element[_state], values);
138
- }
139
-
140
- const state = store(values || {});
141
- const cleanupFunctions = [];
142
- const effects = [];
143
-
144
- initializeElement(element, element.attributes, state, effects, cleanupFunctions);
145
-
146
- if (!(_state in element)) {
147
- element[_state] = state;
148
-
149
- element[_off] = () => {
150
- cleanupFunctions.forEach((cleanup) => cleanup());
151
- cleanupFunctions.length = 0;
152
- };
153
-
154
- element[_on] = () => {
155
- cleanupFunctions.length = 0;
156
- cleanupFunctions.push(...effects.map((effectFunction) => effect(effectFunction)));
157
- };
158
-
159
- element[_dispose] = () => {
160
- element[_off]();
161
- element[_off] = null;
162
- element[_on] = null;
163
- element[_dispose] = null;
164
- element[_state] = null;
165
- };
166
- }
167
-
168
- return state;
169
- };
170
-
171
- sprae.use = (configuration) => {
172
- if (configuration.signal) {
173
- use(configuration);
174
- }
175
- if (configuration.compile) {
176
- compile = configuration.compile;
177
- }
178
- if (configuration.prefix) {
179
- prefix = configuration.prefix;
180
- }
181
- };
182
-
183
- export const frag = (template) => {
184
- if (!template.nodeType) {
185
- return template;
186
- }
187
-
188
- const content = template.content.cloneNode(true);
189
- const attributes = [...template.attributes];
190
- const referenceNode = document.createTextNode("");
191
-
192
- content.append(referenceNode);
193
- const childNodes = [...content.childNodes];
194
-
195
- return {
196
- childNodes,
197
- content,
198
-
199
- remove() {
200
- content.append(...childNodes);
201
- },
202
-
203
- replaceWith(element) {
204
- if (element === referenceNode) return;
205
- referenceNode.before(element);
206
- content.append(...childNodes);
207
- },
208
-
209
- attributes,
210
-
211
- removeAttribute(name) {
212
- const attributeIndex = attributes.findIndex((attr) => attr.name === name);
213
- if (attributeIndex !== -1) {
214
- attributes.splice(attributeIndex, 1);
215
- }
216
- },
217
- };
218
- };
219
-
220
- export default sprae;
@@ -1,9 +0,0 @@
1
- import { dir } from "../core.js";
2
- import { attr, dashcase } from "./default.js";
3
-
4
- dir("aria", (el) => (value) => {
5
- for (const key in value) {
6
- const v = value[key];
7
- attr(el, `aria-${dashcase(key)}`, v == null ? null : String(v));
8
- }
9
- });
@@ -1,28 +0,0 @@
1
- import { dir } from "../core.js";
2
-
3
- dir("class", (el) => {
4
- let previous = new Set();
5
-
6
- return (value) => {
7
- const next = new Set();
8
-
9
- if (value) {
10
- if (typeof value === "string") {
11
- value.split(" ").forEach((cls) => next.add(cls));
12
- } else if (Array.isArray(value)) {
13
- value.forEach((cls) => cls && next.add(cls));
14
- } else {
15
- Object.entries(value).forEach(([name, on]) => on && next.add(name));
16
- }
17
- }
18
-
19
- for (const cls of previous) {
20
- if (next.has(cls)) next.delete(cls);
21
- else el.classList.remove(cls);
22
- }
23
-
24
- for (const cls of next) el.classList.add(cls);
25
-
26
- previous = next;
27
- };
28
- });
@@ -1,5 +0,0 @@
1
- import { dir } from "../core.js";
2
-
3
- dir("data", (el) => (value) => {
4
- for (const key in value) el.dataset[key] = value[key];
5
- });
@@ -1,210 +0,0 @@
1
- // generic property directive
2
- import { dir, err } from "../core.js";
3
-
4
- dir("default", (target, state, expr, name) => {
5
- if (!name.startsWith("on")) {
6
- if (name) {
7
- return (value) => attr(target, name, value);
8
- }
9
- return (value) => {
10
- for (const key in value) attr(target, dashcase(key), value[key]);
11
- };
12
- }
13
-
14
- const contexts = name.split("..").map((part) => parseEventContext(part, target));
15
-
16
- const bind = (fn, ctx) => {
17
- const { evt, target, test, defer, stop, prevent, immediate, ...opts } = ctx;
18
- const handler = (e) => {
19
- try {
20
- if (!test(e)) return;
21
- if (stop) immediate ? e.stopImmediatePropagation() : e.stopPropagation();
22
- if (prevent) e.preventDefault();
23
- fn?.call(state, e);
24
- } catch (error) {
25
- err(error, `:on${evt}`, fn);
26
- }
27
- };
28
- const cb = defer ? defer(handler) : handler;
29
- target.addEventListener(evt, cb, opts);
30
- return () => target.removeEventListener(evt, cb, opts);
31
- };
32
-
33
- if (contexts.length === 1) return (v) => bind(v, contexts[0]);
34
-
35
- let startFn,
36
- nextFn,
37
- off,
38
- idx = 0;
39
-
40
- const nextListener = (fn) => {
41
- off = bind((e) => {
42
- off();
43
- nextFn = fn?.(e);
44
- idx = (idx + 1) % contexts.length;
45
- if (idx) nextListener(nextFn);
46
- else if (startFn) nextListener(startFn);
47
- }, contexts[idx]);
48
- };
49
-
50
- return (value) => {
51
- startFn = value;
52
- if (!off) nextListener(startFn);
53
- return () => {
54
- startFn = null;
55
- };
56
- };
57
- });
58
-
59
- function parseEventContext(part, target) {
60
- const ctx = { evt: "", target, test: () => true };
61
- ctx.evt = (part.startsWith("on") ? part.slice(2) : part).replace(/\.(\w+)?-?([-\w]+)?/g, (_, mod, param = "") => {
62
- ctx.test = mods[mod]?.(ctx, ...param.split("-")) || ctx.test;
63
- return "";
64
- });
65
- return ctx;
66
- }
67
-
68
- // event modifiers
69
- const mods = {
70
- // actions
71
- prevent(ctx) {
72
- ctx.prevent = true;
73
- },
74
- stop(ctx) {
75
- ctx.stop = true;
76
- },
77
- immediate(ctx) {
78
- ctx.immediate = true;
79
- },
80
-
81
- // options
82
- once(ctx) {
83
- ctx.once = true;
84
- },
85
- passive(ctx) {
86
- ctx.passive = true;
87
- },
88
- capture(ctx) {
89
- ctx.capture = true;
90
- },
91
-
92
- // target
93
- window(ctx) {
94
- ctx.target = window;
95
- },
96
- document(ctx) {
97
- ctx.target = document;
98
- },
99
- parent(ctx) {
100
- ctx.target = ctx.target.parentNode;
101
- },
102
-
103
- throttle(ctx, limit = 108) {
104
- ctx.defer = (fn) => throttle(fn, limit);
105
- },
106
- debounce(ctx, wait = 108) {
107
- ctx.defer = (fn) => debounce(fn, wait);
108
- },
109
-
110
- // test
111
- outside: (ctx) => (e) => {
112
- const el = ctx.target;
113
- if (el.contains(e.target)) return false;
114
- if (e.target.isConnected === false) return false;
115
- if (el.offsetWidth < 1 && el.offsetHeight < 1) return false;
116
- return true;
117
- },
118
- self: (ctx) => (e) => e.target === ctx.target,
119
-
120
- // keyboard
121
- ctrl:
122
- (_ctx, ...param) =>
123
- (e) =>
124
- keys.ctrl(e) && param.every((p) => (keys[p] ? keys[p](e) : e.key === p)),
125
- shift:
126
- (_ctx, ...param) =>
127
- (e) =>
128
- keys.shift(e) && param.every((p) => (keys[p] ? keys[p](e) : e.key === p)),
129
- alt:
130
- (_ctx, ...param) =>
131
- (e) =>
132
- keys.alt(e) && param.every((p) => (keys[p] ? keys[p](e) : e.key === p)),
133
- meta:
134
- (_ctx, ...param) =>
135
- (e) =>
136
- keys.meta(e) && param.every((p) => (keys[p] ? keys[p](e) : e.key === p)),
137
- arrow: () => keys.arrow,
138
- enter: () => keys.enter,
139
- esc: () => keys.esc,
140
- tab: () => keys.tab,
141
- space: () => keys.space,
142
- delete: () => keys.delete,
143
- digit: () => keys.digit,
144
- letter: () => keys.letter,
145
- char: () => keys.char,
146
- };
147
-
148
- // key testers
149
- const keys = {
150
- ctrl: (e) => e.ctrlKey || e.key === "Control" || e.key === "Ctrl",
151
- shift: (e) => e.shiftKey || e.key === "Shift",
152
- alt: (e) => e.altKey || e.key === "Alt",
153
- meta: (e) => e.metaKey || e.key === "Meta" || e.key === "Command",
154
- arrow: (e) => e.key.startsWith("Arrow"),
155
- enter: (e) => e.key === "Enter",
156
- esc: (e) => e.key.startsWith("Esc"),
157
- tab: (e) => e.key === "Tab",
158
- space: (e) => e.key === " " || e.key === "Space" || e.key === " ",
159
- delete: (e) => e.key === "Delete" || e.key === "Backspace",
160
- digit: (e) => /^\d$/.test(e.key),
161
- letter: (e) => /^\p{L}$/gu.test(e.key),
162
- char: (e) => /^\S$/.test(e.key),
163
- };
164
-
165
- // create delayed fns
166
- const throttle = (fn, limit) => {
167
- let paused = false;
168
- let planned = false;
169
-
170
- const block = (e) => {
171
- paused = true;
172
- setTimeout(() => {
173
- paused = false;
174
- if (!planned) {
175
- return;
176
- }
177
- planned = false;
178
- block(e);
179
- fn(e);
180
- }, limit);
181
- };
182
-
183
- return (e) => {
184
- if (paused) {
185
- planned = true;
186
- return;
187
- }
188
- block(e);
189
- return fn(e);
190
- };
191
- };
192
-
193
- const debounce = (fn, wait) => {
194
- let timeout;
195
- return (e) => {
196
- clearTimeout(timeout);
197
- timeout = setTimeout(() => {
198
- timeout = null;
199
- fn(e);
200
- }, wait);
201
- };
202
- };
203
-
204
- // set attr
205
- export const attr = (el, name, v) => {
206
- if (v == null || v === false) el.removeAttribute(name);
207
- else el.setAttribute(name, v === true ? "" : typeof v === "number" || typeof v === "string" ? v : "");
208
- };
209
-
210
- export const dashcase = (str) => str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match, i) => (i ? "-" : "") + match.toLowerCase());
@@ -1,100 +0,0 @@
1
- import sprae, { _state, dir, frag, parse } from "../core.js";
2
- import store, { _change, _signals } from "../store.js";
3
- import { effect } from "../signal.js";
4
-
5
- dir(
6
- "each",
7
- (tpl, state, expr) => {
8
- const [itemVar, idxVar = "$"] = expr
9
- .split(/\bin\b/)[0]
10
- .trim()
11
- .split(/\s*,\s*/);
12
-
13
- const holder = document.createTextNode("");
14
-
15
- let current;
16
- let keys;
17
- let items;
18
- let prevLen = 0;
19
-
20
- const render = () => {
21
- const newItems = items;
22
- const newLen = newItems.length;
23
- let i = 0;
24
-
25
- if (current && !current[_change]) {
26
- for (const s of current[_signals] || []) s[Symbol.dispose]();
27
- current = null;
28
- prevLen = 0;
29
- }
30
-
31
- if (newLen < prevLen) {
32
- current.length = newLen;
33
- } else {
34
- if (!current) {
35
- current = newItems;
36
- } else {
37
- while (i < prevLen) {
38
- current[i] = newItems[i];
39
- i++;
40
- }
41
- }
42
-
43
- for (; i < newLen; i++) {
44
- current[i] = newItems[i];
45
- const idx = i;
46
-
47
- const scope = store(
48
- {
49
- [itemVar]: current[_signals]?.[idx] || current[idx],
50
- [idxVar]: keys ? keys[idx] : idx,
51
- },
52
- state,
53
- );
54
-
55
- const el = tpl.content ? frag(tpl) : tpl.cloneNode(true);
56
-
57
- holder.before(el.content || el);
58
- sprae(el, scope);
59
-
60
- const prevDispose = ((current[_signals] ||= [])[i] ||= {})[Symbol.dispose];
61
- current[_signals][i][Symbol.dispose] = () => {
62
- prevDispose?.();
63
- el[Symbol.dispose]?.();
64
- el.remove();
65
- };
66
- }
67
- }
68
-
69
- prevLen = newLen;
70
- };
71
-
72
- tpl.replaceWith(holder);
73
- tpl[_state] = null;
74
-
75
- return (value) => {
76
- keys = null;
77
- if (typeof value === "number") {
78
- items = Array.from({ length: value }, (_, i) => i + 1);
79
- } else if (value?.constructor === Object) {
80
- keys = Object.keys(value);
81
- items = Object.values(value);
82
- } else {
83
- items = value || [];
84
- }
85
-
86
- let planned = 0;
87
- return effect(() => {
88
- items[_change]?.value;
89
- if (!planned++) {
90
- render();
91
- queueMicrotask(() => {
92
- if (planned > 1) render();
93
- planned = 0;
94
- });
95
- }
96
- });
97
- };
98
- },
99
- (expr) => parse(expr.split(/\bin\b/)[1]),
100
- );
@@ -1,3 +0,0 @@
1
- import { dir } from "../core.js";
2
-
3
- dir("fx", (_) => (_) => _);
@@ -1,46 +0,0 @@
1
- import sprae, { dir, _state, _on, _off, frag } from "../core.js";
2
-
3
- // :if is interchangeable with :each depending on order, :if :each or :each :if have different meanings
4
- // as for :if :with - :if must init first, since it is lazy, to avoid initializing component ahead of time by :with
5
- // we consider :with={x} :if={x} case insignificant
6
- const _prevIf = Symbol("if");
7
-
8
- dir("if", (el, state) => {
9
- const holder = document.createTextNode("");
10
-
11
- let nextEl = el.nextElementSibling;
12
- let curEl;
13
- let ifEl;
14
- let elseEl;
15
-
16
- el.replaceWith(holder);
17
-
18
- ifEl = el.content ? frag(el) : el;
19
- ifEl[_state] = null; // mark el as fake-spraed to hold-on init, since we sprae rest when branch matches
20
-
21
- // FIXME: instead of nextEl / el we should use elseEl / ifEl
22
- if (nextEl?.hasAttribute(":else")) {
23
- nextEl.removeAttribute(":else");
24
- // if nextEl is :else :if - leave it for its own :if handler
25
- if (!nextEl.hasAttribute(":if")) (nextEl.remove(), (elseEl = nextEl.content ? frag(nextEl) : nextEl), (elseEl[_state] = null));
26
- } else nextEl = null;
27
-
28
- return (value, newEl = el[_prevIf] ? null : value ? ifEl : elseEl) => {
29
- if (nextEl) nextEl[_prevIf] = el[_prevIf] || newEl == ifEl;
30
- if (curEl != newEl) {
31
- // disable effects on child elements when element is not matched
32
- if (curEl) (curEl.remove(), curEl[_off]?.());
33
- if ((curEl = newEl)) {
34
- holder.before(curEl.content || curEl);
35
- // remove fake memo to sprae as new
36
- if (curEl[_state] === null) {
37
- delete curEl[_state];
38
- sprae(curEl, state);
39
- } else {
40
- // enable effects if branch is matched
41
- curEl[_on]();
42
- }
43
- }
44
- }
45
- };
46
- });
@@ -1,13 +0,0 @@
1
- import { dir, parse } from "../core.js";
2
- import { setter } from "../store.js";
3
-
4
- dir("ref", (el, state, expr) => {
5
- const refTarget = parse(expr)(state);
6
-
7
- if (typeof refTarget === "function") {
8
- return (fn) => fn?.call(null, el);
9
- }
10
-
11
- setter(expr)(state, el);
12
- return (v) => v;
13
- });
@@ -1,22 +0,0 @@
1
- import { dir } from "../core.js";
2
-
3
- dir("style", (el) => {
4
- const initial = el.getAttribute("style") || "";
5
-
6
- return (value) => {
7
- if (typeof value === "string") {
8
- const needsSep = initial && !initial.trim().endsWith(";");
9
- const next = initial + (initial ? (needsSep ? "; " : "") : "") + value;
10
- el.setAttribute("style", next);
11
- return;
12
- }
13
-
14
- if (initial) el.setAttribute("style", initial);
15
-
16
- for (const key in value) {
17
- const val = value[key];
18
- if (key.startsWith("-")) el.style.setProperty(key, val);
19
- else el.style[key] = val;
20
- }
21
- };
22
- });
@@ -1,13 +0,0 @@
1
- import { dir, frag } from "../core.js";
2
-
3
- dir("text", (el) => {
4
- if (el.content) {
5
- const node = frag(el).childNodes[0];
6
- el.replaceWith(node);
7
- el = node;
8
- }
9
-
10
- return (value) => {
11
- el.textContent = value ?? "";
12
- };
13
- });