@fairfox/polly 0.23.0 → 0.25.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 +55 -1
- package/dist/cli/polly.js +21 -1
- package/dist/cli/polly.js.map +3 -3
- package/dist/src/actions/error.d.ts +26 -0
- package/dist/src/actions/event-delegation.d.ts +48 -0
- package/dist/src/actions/form.d.ts +72 -0
- package/dist/src/actions/index.d.ts +13 -0
- package/dist/src/actions/index.js +525 -0
- package/dist/src/actions/index.js.map +15 -0
- package/dist/src/actions/overlay.d.ts +26 -0
- package/dist/src/actions/registry.d.ts +25 -0
- package/dist/src/actions/store.d.ts +26 -0
- package/dist/src/actions/testing.d.ts +26 -0
- package/dist/src/background/index.js +26 -1
- package/dist/src/background/index.js.map +2 -2
- package/dist/src/background/message-router.js +26 -1
- package/dist/src/background/message-router.js.map +2 -2
- package/dist/src/client/index.js +27 -2
- package/dist/src/client/index.js.map +3 -3
- package/dist/src/elysia/index.js +27 -2
- package/dist/src/elysia/index.js.map +3 -3
- package/dist/src/elysia/peer-repo-plugin.d.ts +1 -1
- package/dist/src/index.js +26 -1
- package/dist/src/index.js.map +2 -2
- package/dist/src/mesh-node.d.ts +89 -0
- package/dist/src/mesh-node.js +619 -0
- package/dist/src/mesh-node.js.map +14 -0
- package/dist/src/mesh.d.ts +10 -0
- package/dist/src/mesh.js +951 -24
- package/dist/src/mesh.js.map +17 -9
- package/dist/src/peer.d.ts +1 -0
- package/dist/src/peer.js +130 -84
- package/dist/src/peer.js.map +11 -10
- package/dist/src/polly-ui/ActionForm.d.ts +21 -0
- package/dist/src/polly-ui/ActionInput.d.ts +41 -0
- package/dist/src/polly-ui/ConfirmDialog.d.ts +24 -0
- package/dist/src/polly-ui/Layout.d.ts +51 -0
- package/dist/src/polly-ui/Modal.d.ts +52 -0
- package/dist/src/polly-ui/OverlayRoot.d.ts +10 -0
- package/dist/src/polly-ui/TextInput.d.ts +31 -0
- package/dist/src/polly-ui/Toast.d.ts +19 -0
- package/dist/src/polly-ui/index.css +319 -0
- package/dist/src/polly-ui/index.d.ts +17 -0
- package/dist/src/polly-ui/index.js +953 -0
- package/dist/src/polly-ui/index.js.map +22 -0
- package/dist/src/polly-ui/internal/focus-trap.d.ts +10 -0
- package/dist/src/polly-ui/internal/input-base.d.ts +18 -0
- package/dist/src/polly-ui/internal/scroll-lock.d.ts +9 -0
- package/dist/src/polly-ui/styles.css +70 -0
- package/dist/src/polly-ui/theme.css +163 -0
- package/dist/src/shared/adapters/index.js +26 -1
- package/dist/src/shared/adapters/index.js.map +2 -2
- package/dist/src/shared/lib/blob-cache.d.ts +58 -0
- package/dist/src/shared/lib/blob-store-impl.d.ts +33 -0
- package/dist/src/shared/lib/blob-store.d.ts +87 -0
- package/dist/src/shared/lib/blob-transfer.d.ts +58 -0
- package/dist/src/shared/lib/context-helpers.js +26 -1
- package/dist/src/shared/lib/context-helpers.js.map +2 -2
- package/dist/src/shared/lib/crdt-specialised.d.ts +1 -1
- package/dist/src/shared/lib/crdt-state.d.ts +1 -1
- package/dist/src/shared/lib/errors.js +26 -1
- package/dist/src/shared/lib/errors.js.map +2 -2
- package/dist/src/shared/lib/keyring-storage.d.ts +57 -0
- package/dist/src/shared/lib/mesh-client.d.ts +91 -0
- package/dist/src/shared/lib/mesh-network-adapter.d.ts +1 -1
- package/dist/src/shared/lib/mesh-signaling-client.d.ts +6 -0
- package/dist/src/shared/lib/mesh-state.d.ts +1 -1
- package/dist/src/shared/lib/mesh-webrtc-adapter.d.ts +20 -1
- package/dist/src/shared/lib/message-bus.js +26 -1
- package/dist/src/shared/lib/message-bus.js.map +2 -2
- package/dist/src/shared/lib/peer-relay-adapter.d.ts +1 -1
- package/dist/src/shared/lib/peer-repo-server.d.ts +1 -1
- package/dist/src/shared/lib/peer-state.d.ts +1 -1
- package/dist/src/shared/lib/resource.js +26 -1
- package/dist/src/shared/lib/resource.js.map +2 -2
- package/dist/src/shared/lib/state.js +26 -1
- package/dist/src/shared/lib/state.js.map +2 -2
- package/dist/src/shared/lib/test-helpers.js +26 -1
- package/dist/src/shared/lib/test-helpers.js.map +2 -2
- package/dist/src/shared/lib/wasm-init.d.ts +17 -0
- package/dist/src/shared/state/app-state.js +26 -1
- package/dist/src/shared/state/app-state.js.map +2 -2
- package/dist/src/shared/types/messages.js +26 -1
- package/dist/src/shared/types/messages.js.map +2 -2
- package/dist/tools/quality/src/cli.js +647 -28
- package/dist/tools/quality/src/cli.js.map +11 -5
- package/dist/tools/quality/src/css/check-layout.d.ts +19 -0
- package/dist/tools/quality/src/css/check-quality.d.ts +24 -0
- package/dist/tools/quality/src/css/check-unused.d.ts +20 -0
- package/dist/tools/quality/src/css/check-vars.d.ts +22 -0
- package/dist/tools/quality/src/css/shared.d.ts +33 -0
- package/dist/tools/quality/src/index.d.ts +37 -0
- package/dist/tools/quality/src/index.js +735 -0
- package/dist/tools/quality/src/index.js.map +16 -0
- package/dist/tools/quality/src/logger.d.ts +26 -0
- package/dist/tools/quality/src/no-as-casting.d.ts +44 -0
- package/dist/tools/test/src/adapters/index.js +26 -1
- package/dist/tools/test/src/adapters/index.js.map +2 -2
- package/dist/tools/test/src/browser/index.js +26 -1
- package/dist/tools/test/src/browser/index.js.map +2 -2
- package/dist/tools/test/src/browser/run.js +238 -0
- package/dist/tools/test/src/browser/run.js.map +11 -0
- package/dist/tools/test/src/index.js +26 -1
- package/dist/tools/test/src/index.js.map +2 -2
- package/dist/tools/test/src/test-utils.js +26 -1
- package/dist/tools/test/src/test-utils.js.map +2 -2
- package/dist/tools/test/src/visual/compare.d.ts +23 -0
- package/dist/tools/test/src/visual/harness.d.ts +53 -0
- package/dist/tools/test/src/visual/index.d.ts +12 -0
- package/dist/tools/test/src/visual/index.js +13968 -0
- package/dist/tools/test/src/visual/index.js.map +41 -0
- package/dist/tools/verify/src/cli.js +3 -3
- package/dist/tools/verify/src/cli.js.map +1 -1
- package/dist/tools/verify/src/config.js +26 -1
- package/dist/tools/verify/src/config.js.map +2 -2
- package/package.json +42 -3
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
12
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
20
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
+
for (let key of __getOwnPropNames(mod))
|
|
23
|
+
if (!__hasOwnProp.call(to, key))
|
|
24
|
+
__defProp(to, key, {
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
26
|
+
enumerable: true
|
|
27
|
+
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
30
|
+
return to;
|
|
31
|
+
};
|
|
32
|
+
var __toCommonJS = (from) => {
|
|
33
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
34
|
+
if (entry)
|
|
35
|
+
return entry;
|
|
36
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
37
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
38
|
+
for (var key of __getOwnPropNames(from))
|
|
39
|
+
if (!__hasOwnProp.call(entry, key))
|
|
40
|
+
__defProp(entry, key, {
|
|
41
|
+
get: __accessProp.bind(from, key),
|
|
42
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
__moduleCache.set(from, entry);
|
|
46
|
+
return entry;
|
|
47
|
+
};
|
|
48
|
+
var __moduleCache;
|
|
49
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
50
|
+
var __returnValue = (v) => v;
|
|
51
|
+
function __exportSetter(name, newValue) {
|
|
52
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
53
|
+
}
|
|
54
|
+
var __export = (target, all) => {
|
|
55
|
+
for (var name in all)
|
|
56
|
+
__defProp(target, name, {
|
|
57
|
+
get: all[name],
|
|
58
|
+
enumerable: true,
|
|
59
|
+
configurable: true,
|
|
60
|
+
set: __exportSetter.bind(all, name)
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
64
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
65
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
66
|
+
}) : x)(function(x) {
|
|
67
|
+
if (typeof require !== "undefined")
|
|
68
|
+
return require.apply(this, arguments);
|
|
69
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// src/actions/error.ts
|
|
73
|
+
import { signal } from "@preact/signals";
|
|
74
|
+
var errorState = signal([]);
|
|
75
|
+
var nextId = 0;
|
|
76
|
+
function allocId() {
|
|
77
|
+
nextId += 1;
|
|
78
|
+
return `polly-err-${nextId}`;
|
|
79
|
+
}
|
|
80
|
+
function setError(message, opts = {}) {
|
|
81
|
+
const entry = {
|
|
82
|
+
id: allocId(),
|
|
83
|
+
message,
|
|
84
|
+
severity: opts.severity ?? "error",
|
|
85
|
+
action: opts.action,
|
|
86
|
+
createdAt: Date.now()
|
|
87
|
+
};
|
|
88
|
+
errorState.value = [...errorState.value, entry];
|
|
89
|
+
return entry.id;
|
|
90
|
+
}
|
|
91
|
+
function clearError(id) {
|
|
92
|
+
if (id === undefined) {
|
|
93
|
+
errorState.value = [];
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
errorState.value = errorState.value.filter((e) => e.id !== id);
|
|
97
|
+
}
|
|
98
|
+
function submitError(action, err) {
|
|
99
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
100
|
+
return setError(message, { action, severity: "error" });
|
|
101
|
+
}
|
|
102
|
+
// src/actions/overlay.ts
|
|
103
|
+
import { signal as signal2 } from "@preact/signals";
|
|
104
|
+
var stack = signal2([]);
|
|
105
|
+
function overlayStack() {
|
|
106
|
+
return stack.value;
|
|
107
|
+
}
|
|
108
|
+
function hasOpenOverlay() {
|
|
109
|
+
return stack.value.length > 0;
|
|
110
|
+
}
|
|
111
|
+
function topOverlay() {
|
|
112
|
+
const s = stack.value;
|
|
113
|
+
return s[s.length - 1];
|
|
114
|
+
}
|
|
115
|
+
function pushOverlay(entry) {
|
|
116
|
+
stack.value = [...stack.value, entry];
|
|
117
|
+
}
|
|
118
|
+
function popOverlay(id) {
|
|
119
|
+
const s = stack.value;
|
|
120
|
+
if (s.length === 0)
|
|
121
|
+
return;
|
|
122
|
+
if (id === undefined) {
|
|
123
|
+
const top = s[s.length - 1];
|
|
124
|
+
stack.value = s.slice(0, -1);
|
|
125
|
+
return top;
|
|
126
|
+
}
|
|
127
|
+
const idx = s.findIndex((e) => e.id === id);
|
|
128
|
+
if (idx === -1)
|
|
129
|
+
return;
|
|
130
|
+
const entry = s[idx];
|
|
131
|
+
stack.value = [...s.slice(0, idx), ...s.slice(idx + 1)];
|
|
132
|
+
return entry;
|
|
133
|
+
}
|
|
134
|
+
function closeTopOverlay() {
|
|
135
|
+
const top = topOverlay();
|
|
136
|
+
if (!top)
|
|
137
|
+
return;
|
|
138
|
+
top.onClose?.();
|
|
139
|
+
return popOverlay(top.id);
|
|
140
|
+
}
|
|
141
|
+
function resetOverlayStack() {
|
|
142
|
+
stack.value = [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/actions/event-delegation.ts
|
|
146
|
+
var INTERACTIVE_TAGS = new Set(["BUTTON", "A", "INPUT", "SELECT", "TEXTAREA"]);
|
|
147
|
+
var ACTION_EVENT_TYPES = new Set(["click", "submit", "change", "input"]);
|
|
148
|
+
function parseActionData(element) {
|
|
149
|
+
const data = {};
|
|
150
|
+
for (const attr of Array.from(element.attributes)) {
|
|
151
|
+
if (attr.name.startsWith("data-action-")) {
|
|
152
|
+
const key = attr.name.replace("data-action-", "").replace(/-([a-z])/g, (_m, letter) => letter.toUpperCase());
|
|
153
|
+
data[key] = attr.value;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return data;
|
|
157
|
+
}
|
|
158
|
+
function closeTopOverlay2() {
|
|
159
|
+
const overlays = document.querySelectorAll("[data-overlay-id]");
|
|
160
|
+
if (overlays.length === 0)
|
|
161
|
+
return;
|
|
162
|
+
const topOverlay2 = overlays[overlays.length - 1];
|
|
163
|
+
if (!topOverlay2)
|
|
164
|
+
return;
|
|
165
|
+
topOverlay2.dispatchEvent(new CustomEvent("overlay:close", {
|
|
166
|
+
bubbles: true,
|
|
167
|
+
detail: { id: topOverlay2.getAttribute("data-overlay-id") }
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
function resolveAction(event) {
|
|
171
|
+
const target = event.target;
|
|
172
|
+
if (!(target instanceof Element))
|
|
173
|
+
return null;
|
|
174
|
+
const actionElement = target.closest("[data-action]");
|
|
175
|
+
if (!(actionElement instanceof HTMLElement))
|
|
176
|
+
return null;
|
|
177
|
+
if (event.type === "click" && actionElement.tagName === "FORM")
|
|
178
|
+
return null;
|
|
179
|
+
const action = actionElement.getAttribute("data-action");
|
|
180
|
+
if (!action)
|
|
181
|
+
return null;
|
|
182
|
+
return {
|
|
183
|
+
action,
|
|
184
|
+
element: actionElement,
|
|
185
|
+
event,
|
|
186
|
+
data: parseActionData(actionElement)
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function installEventDelegation(onDispatch, options = {}) {
|
|
190
|
+
const handleActionEvent = (event) => {
|
|
191
|
+
const dispatch = resolveAction(event);
|
|
192
|
+
if (dispatch)
|
|
193
|
+
onDispatch(dispatch);
|
|
194
|
+
};
|
|
195
|
+
const handleKeyDown = (event) => {
|
|
196
|
+
if (event.key === "Escape") {
|
|
197
|
+
if (options.onEscape) {
|
|
198
|
+
options.onEscape();
|
|
199
|
+
} else {
|
|
200
|
+
closeTopOverlay();
|
|
201
|
+
closeTopOverlay2();
|
|
202
|
+
}
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
206
|
+
const target = event.target;
|
|
207
|
+
if (!(target instanceof Element))
|
|
208
|
+
return;
|
|
209
|
+
if (INTERACTIVE_TAGS.has(target.tagName))
|
|
210
|
+
return;
|
|
211
|
+
const dispatch = resolveAction(event);
|
|
212
|
+
if (dispatch) {
|
|
213
|
+
event.preventDefault();
|
|
214
|
+
onDispatch(dispatch);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const handleMouseDown = (event) => {
|
|
219
|
+
if (!(event.target instanceof Element))
|
|
220
|
+
return;
|
|
221
|
+
const clickedOverlay = event.target.closest("[data-overlay-id]");
|
|
222
|
+
if (clickedOverlay)
|
|
223
|
+
return;
|
|
224
|
+
if (options.onOutsideOverlayClick) {
|
|
225
|
+
options.onOutsideOverlayClick();
|
|
226
|
+
} else {
|
|
227
|
+
closeTopOverlay();
|
|
228
|
+
closeTopOverlay2();
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
for (const eventType of ACTION_EVENT_TYPES) {
|
|
232
|
+
document.addEventListener(eventType, handleActionEvent);
|
|
233
|
+
}
|
|
234
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
235
|
+
document.addEventListener("mousedown", handleMouseDown);
|
|
236
|
+
return () => {
|
|
237
|
+
for (const eventType of ACTION_EVENT_TYPES) {
|
|
238
|
+
document.removeEventListener(eventType, handleActionEvent);
|
|
239
|
+
}
|
|
240
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
241
|
+
document.removeEventListener("mousedown", handleMouseDown);
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
// src/actions/form.ts
|
|
245
|
+
import { computed, effect, signal as signal3 } from "@preact/signals";
|
|
246
|
+
function isFormElement(target) {
|
|
247
|
+
if (!target)
|
|
248
|
+
return false;
|
|
249
|
+
if (typeof HTMLFormElement !== "undefined") {
|
|
250
|
+
return target instanceof HTMLFormElement;
|
|
251
|
+
}
|
|
252
|
+
const t = target;
|
|
253
|
+
return typeof t.tagName === "string" && t.tagName === "FORM";
|
|
254
|
+
}
|
|
255
|
+
function createForm(config) {
|
|
256
|
+
const fieldKeys = Object.keys(config.initialValues);
|
|
257
|
+
const fields = {};
|
|
258
|
+
for (const key of fieldKeys) {
|
|
259
|
+
fields[key] = signal3(config.initialValues[key]);
|
|
260
|
+
}
|
|
261
|
+
const values = computed(() => {
|
|
262
|
+
const v = {};
|
|
263
|
+
for (const key of fieldKeys) {
|
|
264
|
+
v[key] = fields[key].value;
|
|
265
|
+
}
|
|
266
|
+
return v;
|
|
267
|
+
});
|
|
268
|
+
const isOpen = signal3(false);
|
|
269
|
+
const isSubmitting = signal3(false);
|
|
270
|
+
const errors = signal3({});
|
|
271
|
+
const openParams = signal3({});
|
|
272
|
+
let getStoresRef = null;
|
|
273
|
+
let disposeGuardEffect = null;
|
|
274
|
+
function getStoresOrThrow() {
|
|
275
|
+
if (!getStoresRef) {
|
|
276
|
+
throw new Error(`Form "${config.name}" used before bindStores(). Call form.bindStores(() => yourStores) at app init.`);
|
|
277
|
+
}
|
|
278
|
+
return getStoresRef();
|
|
279
|
+
}
|
|
280
|
+
function resetToInitial() {
|
|
281
|
+
for (const key of fieldKeys) {
|
|
282
|
+
fields[key].value = config.initialValues[key];
|
|
283
|
+
}
|
|
284
|
+
errors.value = {};
|
|
285
|
+
}
|
|
286
|
+
function open(override, params = {}) {
|
|
287
|
+
resetToInitial();
|
|
288
|
+
openParams.value = params;
|
|
289
|
+
if (config.onOpen && getStoresRef) {
|
|
290
|
+
const onOpenOverride = config.onOpen({ data: params, stores: getStoresOrThrow() }) ?? {};
|
|
291
|
+
for (const [k, v] of Object.entries(onOpenOverride)) {
|
|
292
|
+
if (k in fields) {
|
|
293
|
+
fields[k].value = v;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (override) {
|
|
298
|
+
for (const [k, v] of Object.entries(override)) {
|
|
299
|
+
if (k in fields) {
|
|
300
|
+
fields[k].value = v;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
isOpen.value = true;
|
|
305
|
+
}
|
|
306
|
+
function close() {
|
|
307
|
+
isOpen.value = false;
|
|
308
|
+
resetToInitial();
|
|
309
|
+
openParams.value = {};
|
|
310
|
+
}
|
|
311
|
+
async function submit(event) {
|
|
312
|
+
if (event)
|
|
313
|
+
event.preventDefault();
|
|
314
|
+
if (event && isFormElement(event.target)) {
|
|
315
|
+
const fd = new FormData(event.target);
|
|
316
|
+
for (const key of fieldKeys) {
|
|
317
|
+
const raw = fd.get(key);
|
|
318
|
+
if (raw !== null) {
|
|
319
|
+
fields[key].value = String(raw);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const current = values.value;
|
|
324
|
+
const validationErrors = config.validate?.(current);
|
|
325
|
+
if (validationErrors && Object.keys(validationErrors).length > 0) {
|
|
326
|
+
errors.value = validationErrors;
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
errors.value = {};
|
|
330
|
+
isSubmitting.value = true;
|
|
331
|
+
try {
|
|
332
|
+
await config.onSubmit({
|
|
333
|
+
values: current,
|
|
334
|
+
stores: getStoresOrThrow()
|
|
335
|
+
});
|
|
336
|
+
close();
|
|
337
|
+
} catch (err) {
|
|
338
|
+
submitError(`${config.name}:submit`, err);
|
|
339
|
+
} finally {
|
|
340
|
+
isSubmitting.value = false;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function bindStores(getStores) {
|
|
344
|
+
getStoresRef = getStores;
|
|
345
|
+
disposeGuardEffect?.();
|
|
346
|
+
const guardFn = config.guard;
|
|
347
|
+
if (guardFn) {
|
|
348
|
+
disposeGuardEffect = effect(() => {
|
|
349
|
+
if (!isOpen.value)
|
|
350
|
+
return;
|
|
351
|
+
if (!guardFn({ stores: getStores() }))
|
|
352
|
+
close();
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const actions = {
|
|
357
|
+
[`${config.name}:open`]: ({ data }) => {
|
|
358
|
+
open(undefined, data);
|
|
359
|
+
},
|
|
360
|
+
[`${config.name}:close`]: () => {
|
|
361
|
+
close();
|
|
362
|
+
},
|
|
363
|
+
[`${config.name}:submit`]: async ({ event }) => {
|
|
364
|
+
await submit(event);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
return {
|
|
368
|
+
name: config.name,
|
|
369
|
+
isOpen,
|
|
370
|
+
values,
|
|
371
|
+
fields,
|
|
372
|
+
errors,
|
|
373
|
+
isSubmitting,
|
|
374
|
+
openParams,
|
|
375
|
+
open,
|
|
376
|
+
close,
|
|
377
|
+
submit,
|
|
378
|
+
bindStores,
|
|
379
|
+
actions
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function createFormSet(forms) {
|
|
383
|
+
const merged = {};
|
|
384
|
+
for (const form of forms) {
|
|
385
|
+
Object.assign(merged, form.actions);
|
|
386
|
+
}
|
|
387
|
+
const openForm = computed(() => {
|
|
388
|
+
for (const form of forms) {
|
|
389
|
+
if (form.isOpen.value)
|
|
390
|
+
return form.name;
|
|
391
|
+
}
|
|
392
|
+
return null;
|
|
393
|
+
});
|
|
394
|
+
return {
|
|
395
|
+
actions: merged,
|
|
396
|
+
openForm,
|
|
397
|
+
closeAll() {
|
|
398
|
+
for (const form of forms)
|
|
399
|
+
form.close();
|
|
400
|
+
},
|
|
401
|
+
bindStores(getStores) {
|
|
402
|
+
for (const form of forms)
|
|
403
|
+
form.bindStores(getStores);
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
// src/actions/store.tsx
|
|
408
|
+
import { createContext } from "preact";
|
|
409
|
+
import { useContext } from "preact/hooks";
|
|
410
|
+
import { jsxDEV } from "preact/jsx-dev-runtime";
|
|
411
|
+
function createStore(init) {
|
|
412
|
+
return init;
|
|
413
|
+
}
|
|
414
|
+
var StoreContext = createContext(null);
|
|
415
|
+
function StoreProvider({ children, stores }) {
|
|
416
|
+
return /* @__PURE__ */ jsxDEV(StoreContext.Provider, {
|
|
417
|
+
value: stores,
|
|
418
|
+
children
|
|
419
|
+
}, undefined, false, undefined, this);
|
|
420
|
+
}
|
|
421
|
+
function useStores() {
|
|
422
|
+
const ctx = useContext(StoreContext);
|
|
423
|
+
if (ctx === null) {
|
|
424
|
+
throw new Error("useStores must be used within a StoreProvider");
|
|
425
|
+
}
|
|
426
|
+
return ctx;
|
|
427
|
+
}
|
|
428
|
+
// src/actions/testing.ts
|
|
429
|
+
function createMockElement(attrs = {}, tagName = "DIV") {
|
|
430
|
+
const attrMap = new Map(Object.entries(attrs));
|
|
431
|
+
const el = {
|
|
432
|
+
tagName,
|
|
433
|
+
nodeType: 1,
|
|
434
|
+
getAttribute: (name) => attrMap.get(name) ?? null,
|
|
435
|
+
setAttribute: (name, value) => {
|
|
436
|
+
attrMap.set(name, value);
|
|
437
|
+
},
|
|
438
|
+
hasAttribute: (name) => attrMap.has(name),
|
|
439
|
+
removeAttribute: (name) => {
|
|
440
|
+
attrMap.delete(name);
|
|
441
|
+
},
|
|
442
|
+
attributes: Array.from(attrMap.entries()).map(([name, value]) => ({
|
|
443
|
+
name,
|
|
444
|
+
value
|
|
445
|
+
}))
|
|
446
|
+
};
|
|
447
|
+
return el;
|
|
448
|
+
}
|
|
449
|
+
function createMockSubmitEvent(form) {
|
|
450
|
+
let target;
|
|
451
|
+
if (typeof HTMLFormElement !== "undefined" && form instanceof HTMLFormElement) {
|
|
452
|
+
target = form;
|
|
453
|
+
} else {
|
|
454
|
+
target = createMockFormElement(form);
|
|
455
|
+
}
|
|
456
|
+
let defaultPrevented = false;
|
|
457
|
+
return {
|
|
458
|
+
type: "submit",
|
|
459
|
+
target,
|
|
460
|
+
currentTarget: target,
|
|
461
|
+
preventDefault() {
|
|
462
|
+
defaultPrevented = true;
|
|
463
|
+
},
|
|
464
|
+
stopPropagation() {},
|
|
465
|
+
get defaultPrevented() {
|
|
466
|
+
return defaultPrevented;
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
function createMockFormElement(fields) {
|
|
471
|
+
return {
|
|
472
|
+
nodeType: 1,
|
|
473
|
+
tagName: "FORM",
|
|
474
|
+
elements: Object.entries(fields).map(([name, value]) => ({
|
|
475
|
+
name,
|
|
476
|
+
value
|
|
477
|
+
}))
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
function createMockStores(partial = {}) {
|
|
481
|
+
return partial;
|
|
482
|
+
}
|
|
483
|
+
async function runAction(registry, action, ctx) {
|
|
484
|
+
const handler = registry[action];
|
|
485
|
+
if (!handler) {
|
|
486
|
+
throw new Error(`No handler registered for action "${action}"`);
|
|
487
|
+
}
|
|
488
|
+
const fullCtx = {
|
|
489
|
+
stores: ctx.stores,
|
|
490
|
+
event: ctx.event ?? new Event("click"),
|
|
491
|
+
element: ctx.element ?? createMockElement(),
|
|
492
|
+
data: ctx.data ?? {}
|
|
493
|
+
};
|
|
494
|
+
await handler(fullCtx);
|
|
495
|
+
}
|
|
496
|
+
export {
|
|
497
|
+
useStores,
|
|
498
|
+
topOverlay,
|
|
499
|
+
submitError,
|
|
500
|
+
setError,
|
|
501
|
+
runAction,
|
|
502
|
+
resolveAction,
|
|
503
|
+
resetOverlayStack,
|
|
504
|
+
pushOverlay,
|
|
505
|
+
popOverlay,
|
|
506
|
+
parseActionData,
|
|
507
|
+
overlayStack,
|
|
508
|
+
installEventDelegation,
|
|
509
|
+
hasOpenOverlay,
|
|
510
|
+
errorState,
|
|
511
|
+
createStore,
|
|
512
|
+
createMockSubmitEvent,
|
|
513
|
+
createMockStores,
|
|
514
|
+
createMockElement,
|
|
515
|
+
createFormSet,
|
|
516
|
+
createForm,
|
|
517
|
+
closeTopOverlay2 as closeTopOverlayViaDom,
|
|
518
|
+
closeTopOverlay,
|
|
519
|
+
clearError,
|
|
520
|
+
StoreProvider,
|
|
521
|
+
INTERACTIVE_TAGS,
|
|
522
|
+
ACTION_EVENT_TYPES
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
//# debugId=EEBB85AB8D494BC664756E2164756E21
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/actions/error.ts", "../src/actions/overlay.ts", "../src/actions/event-delegation.ts", "../src/actions/form.ts", "../src/actions/store.tsx", "../src/actions/testing.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Global error surface for action handlers.\n *\n * Actions that fail set the errorState signal via `submitError`; a `<Toast>`\n * component consumes that signal and renders a dismissable message. Handlers\n * that catch expected failures (validation, quota) may call `setError` directly.\n */\n\nimport { signal } from \"@preact/signals\";\n\nexport type ErrorSeverity = \"error\" | \"warning\" | \"info\";\n\nexport type ErrorEntry = {\n id: string;\n message: string;\n severity: ErrorSeverity;\n action?: string;\n createdAt: number;\n};\n\nexport const errorState = signal<ErrorEntry[]>([]);\n\nlet nextId = 0;\nfunction allocId(): string {\n nextId += 1;\n return `polly-err-${nextId}`;\n}\n\nexport function setError(\n message: string,\n opts: { severity?: ErrorSeverity; action?: string } = {}\n): string {\n const entry: ErrorEntry = {\n id: allocId(),\n message,\n severity: opts.severity ?? \"error\",\n action: opts.action,\n createdAt: Date.now(),\n };\n errorState.value = [...errorState.value, entry];\n return entry.id;\n}\n\nexport function clearError(id?: string): void {\n if (id === undefined) {\n errorState.value = [];\n return;\n }\n errorState.value = errorState.value.filter((e) => e.id !== id);\n}\n\n/**\n * Convenience wrapper for an action's catch block.\n * Logs the action name + error and surfaces a user-visible message.\n */\nexport function submitError(action: string, err: unknown): string {\n const message = err instanceof Error ? err.message : String(err);\n return setError(message, { action, severity: \"error\" });\n}\n",
|
|
6
|
+
"/**\n * Overlay registry.\n *\n * Tracks the stack of open overlays (modals, popovers, confirm dialogs) so\n * Escape can close the topmost and `data-overlay-id`-based DOM scans have\n * a mirror in memory. Each entry stores an id plus an optional close\n * callback; `closeTopOverlay` invokes the callback and pops the entry.\n */\n\nimport { signal } from \"@preact/signals\";\n\nexport type OverlayEntry = {\n id: string;\n onClose?: () => void;\n};\n\nconst stack = signal<OverlayEntry[]>([]);\n\n/** Current overlay stack as a read-only snapshot. */\nexport function overlayStack(): readonly OverlayEntry[] {\n return stack.value;\n}\n\n/** Is any overlay currently open? */\nexport function hasOpenOverlay(): boolean {\n return stack.value.length > 0;\n}\n\n/** The top of the stack, or undefined if empty. */\nexport function topOverlay(): OverlayEntry | undefined {\n const s = stack.value;\n return s[s.length - 1];\n}\n\n/** Push an overlay onto the stack. Returns the popped entry when closed. */\nexport function pushOverlay(entry: OverlayEntry): void {\n stack.value = [...stack.value, entry];\n}\n\n/** Pop a specific overlay by id (or the top if no id given). */\nexport function popOverlay(id?: string): OverlayEntry | undefined {\n const s = stack.value;\n if (s.length === 0) return undefined;\n if (id === undefined) {\n const top = s[s.length - 1];\n stack.value = s.slice(0, -1);\n return top;\n }\n const idx = s.findIndex((e) => e.id === id);\n if (idx === -1) return undefined;\n const entry = s[idx];\n stack.value = [...s.slice(0, idx), ...s.slice(idx + 1)];\n return entry;\n}\n\n/** Close the top overlay by calling its onClose and popping it. */\nexport function closeTopOverlay(): OverlayEntry | undefined {\n const top = topOverlay();\n if (!top) return undefined;\n top.onClose?.();\n return popOverlay(top.id);\n}\n\n/** Reset the stack. Intended for tests. */\nexport function resetOverlayStack(): void {\n stack.value = [];\n}\n",
|
|
7
|
+
"/**\n * Event delegation core.\n *\n * One document listener dispatches `data-action` events to typed handlers.\n * Walks up the DOM with `closest('[data-action]')`, parses `data-action-*`\n * attributes into a camelCase object, and hands the dispatch to the caller.\n *\n * Forms are skipped on click — a `<form data-action=\"...\">` responds to\n * submit only, so clicks on form children don't bubble into its action.\n * Escape closes the topmost overlay by calling the overlay registry.\n * Enter/Space on non-interactive elements with `data-action` fire the\n * action (Space is prevented to stop page scroll). Click outside any\n * `[data-overlay-id]` element also pops the top overlay.\n */\n\nimport { closeTopOverlay as closeTopRegistryOverlay } from \"./overlay.ts\";\n\n/** Elements that natively fire click on Enter/Space. */\nexport const INTERACTIVE_TAGS = new Set([\"BUTTON\", \"A\", \"INPUT\", \"SELECT\", \"TEXTAREA\"]);\n\n/** Event types that may trigger a data-action dispatch. */\nexport const ACTION_EVENT_TYPES = new Set([\"click\", \"submit\", \"change\", \"input\"]);\n\n/** Parsed action dispatch — what the runtime resolves before invoking a handler. */\nexport type ActionDispatch = {\n action: string;\n element: HTMLElement;\n event: Event;\n data: Record<string, string>;\n};\n\n/**\n * Parse `data-action-*` attributes into camelCase key-value pairs.\n *\n * `data-action-text-set-id=\"42\"` becomes `{ textSetId: \"42\" }`.\n */\nexport function parseActionData(element: HTMLElement): Record<string, string> {\n const data: Record<string, string> = {};\n for (const attr of Array.from(element.attributes)) {\n if (attr.name.startsWith(\"data-action-\")) {\n const key = attr.name\n .replace(\"data-action-\", \"\")\n .replace(/-([a-z])/g, (_m: string, letter: string) => letter.toUpperCase());\n data[key] = attr.value;\n }\n }\n return data;\n}\n\n/**\n * Close the topmost overlay by dispatching `overlay:close` on its element.\n * Overlays mark themselves with `data-overlay-id`.\n */\nexport function closeTopOverlay(): void {\n const overlays = document.querySelectorAll(\"[data-overlay-id]\");\n if (overlays.length === 0) return;\n const topOverlay = overlays[overlays.length - 1];\n if (!topOverlay) return;\n topOverlay.dispatchEvent(\n new CustomEvent(\"overlay:close\", {\n bubbles: true,\n detail: { id: topOverlay.getAttribute(\"data-overlay-id\") },\n })\n );\n}\n\n/**\n * Resolve a DOM event to an ActionDispatch, or null if no data-action matches.\n */\nexport function resolveAction(event: Event): ActionDispatch | null {\n const target = event.target;\n if (!(target instanceof Element)) return null;\n const actionElement = target.closest(\"[data-action]\");\n if (!(actionElement instanceof HTMLElement)) return null;\n if (event.type === \"click\" && actionElement.tagName === \"FORM\") return null;\n const action = actionElement.getAttribute(\"data-action\");\n if (!action) return null;\n return {\n action,\n element: actionElement,\n event,\n data: parseActionData(actionElement),\n };\n}\n\n/**\n * Install document-level listeners for the delegation system.\n * Returns a cleanup function that removes every listener it installed.\n */\nexport function installEventDelegation(\n onDispatch: (dispatch: ActionDispatch) => void,\n options: { onEscape?: () => void; onOutsideOverlayClick?: () => void } = {}\n): () => void {\n const handleActionEvent = (event: Event) => {\n const dispatch = resolveAction(event);\n if (dispatch) onDispatch(dispatch);\n };\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n if (options.onEscape) {\n options.onEscape();\n } else {\n closeTopRegistryOverlay();\n closeTopOverlay();\n }\n return;\n }\n if (event.key === \"Enter\" || event.key === \" \") {\n const target = event.target;\n if (!(target instanceof Element)) return;\n if (INTERACTIVE_TAGS.has(target.tagName)) return;\n const dispatch = resolveAction(event);\n if (dispatch) {\n event.preventDefault();\n onDispatch(dispatch);\n }\n }\n };\n\n const handleMouseDown = (event: MouseEvent) => {\n if (!(event.target instanceof Element)) return;\n const clickedOverlay = event.target.closest(\"[data-overlay-id]\");\n if (clickedOverlay) return;\n if (options.onOutsideOverlayClick) {\n options.onOutsideOverlayClick();\n } else {\n closeTopRegistryOverlay();\n closeTopOverlay();\n }\n };\n\n for (const eventType of ACTION_EVENT_TYPES) {\n document.addEventListener(eventType, handleActionEvent);\n }\n document.addEventListener(\"keydown\", handleKeyDown);\n document.addEventListener(\"mousedown\", handleMouseDown);\n\n return () => {\n for (const eventType of ACTION_EVENT_TYPES) {\n document.removeEventListener(eventType, handleActionEvent);\n }\n document.removeEventListener(\"keydown\", handleKeyDown);\n document.removeEventListener(\"mousedown\", handleMouseDown);\n };\n}\n",
|
|
8
|
+
"/**\n * Form primitive.\n *\n * `createForm` returns a typed form store: per-field signals, an aggregated\n * values signal, open/close/submit methods, and three auto-registered action\n * handlers (`{name}:open`, `{name}:close`, `{name}:submit`) that callers spread\n * into their global action registry.\n *\n * Lifecycle:\n * open → resets fields to initialValues, applies onOpen overrides,\n * sets isOpen = true, records data-action-* data in openParams\n * typing → uncontrolled inputs keep their own state; controlled inputs\n * write fields.X.value directly\n * submit → reads FormData from the form element, merges into fields,\n * runs optional validate, calls user onSubmit with final values,\n * closes on success, sets errorState on failure\n * close → resets and sets isOpen = false\n * guard → autonomous effect; if guard() returns false while open, close()\n */\n\nimport { computed, effect, type ReadonlySignal, type Signal, signal } from \"@preact/signals\";\nimport { submitError } from \"./error.ts\";\nimport type { ActionRegistry } from \"./registry.ts\";\n\nfunction isFormElement(target: EventTarget | null): target is HTMLFormElement {\n if (!target) return false;\n if (typeof HTMLFormElement !== \"undefined\") {\n return target instanceof HTMLFormElement;\n }\n const t = target as unknown as { tagName?: unknown };\n return typeof t.tagName === \"string\" && t.tagName === \"FORM\";\n}\n\nexport type FormOpenContext<TStores> = {\n data: Record<string, string>;\n stores: TStores;\n};\n\nexport type FormSubmitContext<TValues, TStores> = {\n values: TValues;\n stores: TStores;\n};\n\nexport type FormConfig<TValues extends Record<string, string>, TStores> = {\n /** Used as action namespace: `{name}:open`, `{name}:close`, `{name}:submit`. */\n name: string;\n initialValues: TValues;\n onSubmit: (ctx: FormSubmitContext<TValues, TStores>) => void | Promise<void>;\n /** Invoked on open; return partial overrides to pre-populate fields. */\n onOpen?: (ctx: FormOpenContext<TStores>) => Partial<TValues> | undefined;\n /** Returns false while open → form auto-closes (entity-deletion guard). */\n guard?: (ctx: { stores: TStores }) => boolean;\n /** Synchronous validation. Returning keys blocks submit. */\n validate?: (values: TValues) => Partial<Record<keyof TValues, string>> | null;\n};\n\nexport type FormStore<TValues extends Record<string, string>, TStores> = {\n readonly name: string;\n readonly isOpen: ReadonlySignal<boolean>;\n readonly values: ReadonlySignal<TValues>;\n readonly fields: { [K in keyof TValues]: Signal<TValues[K]> };\n readonly errors: ReadonlySignal<Partial<Record<keyof TValues, string>>>;\n readonly isSubmitting: ReadonlySignal<boolean>;\n readonly openParams: ReadonlySignal<Record<string, string>>;\n open(override?: Partial<TValues>, params?: Record<string, string>): void;\n close(): void;\n submit(event?: Event): Promise<void>;\n /** Late-binds stores; required before guard/onOpen/onSubmit can access stores. */\n bindStores(getStores: () => TStores): void;\n /** Action handler entries. Spread into the user's ActionRegistry. */\n actions: ActionRegistry<TStores>;\n};\n\nexport function createForm<TValues extends Record<string, string>, TStores>(\n config: FormConfig<TValues, TStores>\n): FormStore<TValues, TStores> {\n const fieldKeys = Object.keys(config.initialValues) as unknown as (keyof TValues)[];\n\n const fields = {} as unknown as { [K in keyof TValues]: Signal<TValues[K]> };\n for (const key of fieldKeys) {\n fields[key] = signal(config.initialValues[key]);\n }\n\n const values = computed<TValues>(() => {\n const v = {} as unknown as TValues;\n for (const key of fieldKeys) {\n v[key] = fields[key].value;\n }\n return v;\n });\n\n const isOpen = signal(false);\n const isSubmitting = signal(false);\n const errors = signal<Partial<Record<keyof TValues, string>>>({});\n const openParams = signal<Record<string, string>>({});\n\n let getStoresRef: (() => TStores) | null = null;\n let disposeGuardEffect: (() => void) | null = null;\n\n function getStoresOrThrow(): TStores {\n if (!getStoresRef) {\n throw new Error(\n `Form \"${config.name}\" used before bindStores(). Call form.bindStores(() => yourStores) at app init.`\n );\n }\n return getStoresRef();\n }\n\n function resetToInitial(): void {\n for (const key of fieldKeys) {\n fields[key].value = config.initialValues[key];\n }\n errors.value = {};\n }\n\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: open applies two layers of overrides plus optional onOpen — branchy by design.\n function open(override?: Partial<TValues>, params: Record<string, string> = {}): void {\n resetToInitial();\n openParams.value = params;\n if (config.onOpen && getStoresRef) {\n const onOpenOverride = config.onOpen({ data: params, stores: getStoresOrThrow() }) ?? {};\n for (const [k, v] of Object.entries(onOpenOverride)) {\n if (k in fields) {\n fields[k as keyof TValues].value = v as TValues[keyof TValues];\n }\n }\n }\n if (override) {\n for (const [k, v] of Object.entries(override)) {\n if (k in fields) {\n fields[k as keyof TValues].value = v as TValues[keyof TValues];\n }\n }\n }\n isOpen.value = true;\n }\n\n function close(): void {\n isOpen.value = false;\n resetToInitial();\n openParams.value = {};\n }\n\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: submit funnels FormData, validate, onSubmit, errorState, and close — one-path-per-stage by design.\n async function submit(event?: Event): Promise<void> {\n if (event) event.preventDefault();\n if (event && isFormElement(event.target)) {\n const fd = new FormData(event.target);\n for (const key of fieldKeys) {\n const raw = fd.get(key as unknown as string);\n if (raw !== null) {\n fields[key].value = String(raw) as unknown as TValues[typeof key];\n }\n }\n }\n const current = values.value;\n const validationErrors = config.validate?.(current);\n if (validationErrors && Object.keys(validationErrors).length > 0) {\n errors.value = validationErrors;\n return;\n }\n errors.value = {};\n isSubmitting.value = true;\n try {\n await config.onSubmit({\n values: current,\n stores: getStoresOrThrow(),\n });\n close();\n } catch (err) {\n submitError(`${config.name}:submit`, err);\n } finally {\n isSubmitting.value = false;\n }\n }\n\n function bindStores(getStores: () => TStores): void {\n getStoresRef = getStores;\n disposeGuardEffect?.();\n const guardFn = config.guard;\n if (guardFn) {\n disposeGuardEffect = effect(() => {\n if (!isOpen.value) return;\n if (!guardFn({ stores: getStores() })) close();\n });\n }\n }\n\n const actions: ActionRegistry<TStores> = {\n [`${config.name}:open`]: ({ data }) => {\n open(undefined, data);\n },\n [`${config.name}:close`]: () => {\n close();\n },\n [`${config.name}:submit`]: async ({ event }) => {\n await submit(event);\n },\n };\n\n return {\n name: config.name,\n isOpen,\n values,\n fields,\n errors,\n isSubmitting,\n openParams,\n open,\n close,\n submit,\n bindStores,\n actions,\n };\n}\n\n/**\n * Compose many forms into a single set: merged actions, closeAll, openForm signal.\n */\nexport type FormSet<TStores> = {\n actions: ActionRegistry<TStores>;\n openForm: ReadonlySignal<string | null>;\n closeAll(): void;\n bindStores(getStores: () => TStores): void;\n};\n\nexport function createFormSet<TStores>(\n forms: readonly FormStore<Record<string, string>, TStores>[]\n): FormSet<TStores> {\n const merged: ActionRegistry<TStores> = {};\n for (const form of forms) {\n Object.assign(merged, form.actions);\n }\n\n const openForm = computed<string | null>(() => {\n for (const form of forms) {\n if (form.isOpen.value) return form.name;\n }\n return null;\n });\n\n return {\n actions: merged,\n openForm,\n closeAll() {\n for (const form of forms) form.close();\n },\n bindStores(getStores) {\n for (const form of forms) form.bindStores(getStores);\n },\n };\n}\n",
|
|
9
|
+
"/**\n * Store base + Preact context.\n *\n * `createStore` is a thin convention-enforcer: a typed bag of signals\n * plus methods. Apps use it to define domain stores whose only public\n * mutation surface is named methods (actions call them; components read\n * signals and dispatch actions). No cleverness — a named pattern more\n * than a library.\n *\n * `StoreProvider` + `useStores` wire the stores bag into Preact context\n * so components and the delegation runtime can reach it via a single hook.\n */\n\nimport type { ComponentChildren } from \"preact\";\nimport { createContext } from \"preact\";\nimport { useContext } from \"preact/hooks\";\n\n/**\n * Identity helper; exists so domain stores have a canonical creation\n * call even though the bag itself is a plain object. Centralising here\n * lets us hang additional behaviour off the call site later (debug\n * instrumentation, devtools) without a migration.\n */\nexport function createStore<T extends object>(init: T): T {\n return init;\n}\n\nconst StoreContext = createContext<unknown>(null);\n\nexport type StoreProviderProps<TStores> = {\n children: ComponentChildren;\n stores: TStores;\n};\n\nexport function StoreProvider<TStores>({ children, stores }: StoreProviderProps<TStores>) {\n return <StoreContext.Provider value={stores}>{children}</StoreContext.Provider>;\n}\n\nexport function useStores<TStores>(): TStores {\n const ctx = useContext(StoreContext) as unknown as TStores | null;\n if (ctx === null) {\n throw new Error(\"useStores must be used within a StoreProvider\");\n }\n return ctx;\n}\n",
|
|
10
|
+
"/**\n * Testing helpers for action handlers.\n *\n * Handlers are plain functions taking an ActionContext; these helpers\n * build the context pieces without jsdom. For full-DOM tests use the\n * browser harness at `@fairfox/polly/test/browser`.\n */\n\nimport type { ActionContext, ActionHandler, ActionRegistry } from \"./registry.ts\";\n\n/**\n * Build a mock element that satisfies ActionContext.element.\n * Only the surface a handler is likely to touch is populated.\n */\nexport function createMockElement(\n attrs: Record<string, string> = {},\n tagName = \"DIV\"\n): HTMLElement {\n const attrMap = new Map(Object.entries(attrs));\n const el = {\n tagName,\n nodeType: 1,\n getAttribute: (name: string) => attrMap.get(name) ?? null,\n setAttribute: (name: string, value: string) => {\n attrMap.set(name, value);\n },\n hasAttribute: (name: string) => attrMap.has(name),\n removeAttribute: (name: string) => {\n attrMap.delete(name);\n },\n attributes: Array.from(attrMap.entries()).map(([name, value]) => ({\n name,\n value,\n })),\n } as unknown as HTMLElement;\n return el;\n}\n\n/** Build a minimal submit-like event wrapping a `<form>` FormData payload. */\nexport function createMockSubmitEvent(form: HTMLFormElement | Record<string, string>): Event {\n let target: HTMLFormElement;\n if (typeof HTMLFormElement !== \"undefined\" && form instanceof HTMLFormElement) {\n target = form;\n } else {\n target = createMockFormElement(form as unknown as Record<string, string>);\n }\n let defaultPrevented = false;\n return {\n type: \"submit\",\n target,\n currentTarget: target,\n preventDefault() {\n defaultPrevented = true;\n },\n stopPropagation() {\n /* noop */\n },\n get defaultPrevented() {\n return defaultPrevented;\n },\n } as unknown as Event;\n}\n\nfunction createMockFormElement(fields: Record<string, string>): HTMLFormElement {\n return {\n nodeType: 1,\n tagName: \"FORM\",\n elements: Object.entries(fields).map(([name, value]) => ({\n name,\n value,\n })),\n } as unknown as HTMLFormElement;\n}\n\n/**\n * Shallow-merged partial stores. Callers typically pass signal-backed fakes.\n */\nexport function createMockStores<TStores extends object>(partial: Partial<TStores> = {}): TStores {\n return partial as unknown as TStores;\n}\n\n/**\n * Run a handler in isolation. Useful for unit-testing action logic without\n * wiring the full document event delegation.\n */\nexport async function runAction<TStores>(\n registry: ActionRegistry<TStores>,\n action: string,\n ctx: Partial<ActionContext<TStores>> & { stores: TStores }\n): Promise<void> {\n const handler: ActionHandler<TStores> | undefined = registry[action];\n if (!handler) {\n throw new Error(`No handler registered for action \"${action}\"`);\n }\n const fullCtx: ActionContext<TStores> = {\n stores: ctx.stores,\n event: ctx.event ?? new Event(\"click\"),\n element: ctx.element ?? createMockElement(),\n data: ctx.data ?? {},\n };\n await handler(fullCtx);\n}\n"
|
|
11
|
+
],
|
|
12
|
+
"mappings": "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",
|
|
13
|
+
"debugId": "EEBB85AB8D494BC664756E2164756E21",
|
|
14
|
+
"names": []
|
|
15
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overlay registry.
|
|
3
|
+
*
|
|
4
|
+
* Tracks the stack of open overlays (modals, popovers, confirm dialogs) so
|
|
5
|
+
* Escape can close the topmost and `data-overlay-id`-based DOM scans have
|
|
6
|
+
* a mirror in memory. Each entry stores an id plus an optional close
|
|
7
|
+
* callback; `closeTopOverlay` invokes the callback and pops the entry.
|
|
8
|
+
*/
|
|
9
|
+
export type OverlayEntry = {
|
|
10
|
+
id: string;
|
|
11
|
+
onClose?: () => void;
|
|
12
|
+
};
|
|
13
|
+
/** Current overlay stack as a read-only snapshot. */
|
|
14
|
+
export declare function overlayStack(): readonly OverlayEntry[];
|
|
15
|
+
/** Is any overlay currently open? */
|
|
16
|
+
export declare function hasOpenOverlay(): boolean;
|
|
17
|
+
/** The top of the stack, or undefined if empty. */
|
|
18
|
+
export declare function topOverlay(): OverlayEntry | undefined;
|
|
19
|
+
/** Push an overlay onto the stack. Returns the popped entry when closed. */
|
|
20
|
+
export declare function pushOverlay(entry: OverlayEntry): void;
|
|
21
|
+
/** Pop a specific overlay by id (or the top if no id given). */
|
|
22
|
+
export declare function popOverlay(id?: string): OverlayEntry | undefined;
|
|
23
|
+
/** Close the top overlay by calling its onClose and popping it. */
|
|
24
|
+
export declare function closeTopOverlay(): OverlayEntry | undefined;
|
|
25
|
+
/** Reset the stack. Intended for tests. */
|
|
26
|
+
export declare function resetOverlayStack(): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action registry types.
|
|
3
|
+
*
|
|
4
|
+
* An action registry is a plain object mapping action names to handlers.
|
|
5
|
+
* Handlers receive a typed context with the app's stores, the DOM event,
|
|
6
|
+
* the element carrying `data-action`, and parsed `data-action-*` data.
|
|
7
|
+
* Handlers return void or a Promise for async work.
|
|
8
|
+
*
|
|
9
|
+
* Users compose registries by spreading partial registries (e.g. from
|
|
10
|
+
* `createForm`) into one central object:
|
|
11
|
+
*
|
|
12
|
+
* const ACTION_REGISTRY: ActionRegistry<RootStore> = {
|
|
13
|
+
* ...teamForm.actions,
|
|
14
|
+
* ...projectForm.actions,
|
|
15
|
+
* 'app:theme:toggle': ({ stores }) => { ... },
|
|
16
|
+
* }
|
|
17
|
+
*/
|
|
18
|
+
export type ActionContext<TStores> = {
|
|
19
|
+
stores: TStores;
|
|
20
|
+
event: Event;
|
|
21
|
+
element: HTMLElement;
|
|
22
|
+
data: Record<string, string>;
|
|
23
|
+
};
|
|
24
|
+
export type ActionHandler<TStores> = (ctx: ActionContext<TStores>) => void | Promise<void>;
|
|
25
|
+
export type ActionRegistry<TStores> = Record<string, ActionHandler<TStores>>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store base + Preact context.
|
|
3
|
+
*
|
|
4
|
+
* `createStore` is a thin convention-enforcer: a typed bag of signals
|
|
5
|
+
* plus methods. Apps use it to define domain stores whose only public
|
|
6
|
+
* mutation surface is named methods (actions call them; components read
|
|
7
|
+
* signals and dispatch actions). No cleverness — a named pattern more
|
|
8
|
+
* than a library.
|
|
9
|
+
*
|
|
10
|
+
* `StoreProvider` + `useStores` wire the stores bag into Preact context
|
|
11
|
+
* so components and the delegation runtime can reach it via a single hook.
|
|
12
|
+
*/
|
|
13
|
+
import type { ComponentChildren } from "preact";
|
|
14
|
+
/**
|
|
15
|
+
* Identity helper; exists so domain stores have a canonical creation
|
|
16
|
+
* call even though the bag itself is a plain object. Centralising here
|
|
17
|
+
* lets us hang additional behaviour off the call site later (debug
|
|
18
|
+
* instrumentation, devtools) without a migration.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createStore<T extends object>(init: T): T;
|
|
21
|
+
export type StoreProviderProps<TStores> = {
|
|
22
|
+
children: ComponentChildren;
|
|
23
|
+
stores: TStores;
|
|
24
|
+
};
|
|
25
|
+
export declare function StoreProvider<TStores>({ children, stores }: StoreProviderProps<TStores>): import("preact").JSX.Element;
|
|
26
|
+
export declare function useStores<TStores>(): TStores;
|