foldkit-primitives 0.8.0 → 0.10.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 +32 -2
- package/dist/device/index.d.mts +66 -33
- package/dist/device/index.d.mts.map +1 -1
- package/dist/device/index.mjs +34 -34
- package/dist/device/index.mjs.map +1 -1
- package/dist/dom/index.d.mts +78 -18
- package/dist/dom/index.d.mts.map +1 -1
- package/dist/dom/index.mjs +56 -22
- package/dist/dom/index.mjs.map +1 -1
- package/dist/{editable-text-BbsPOATY.d.mts → editable-text-Dvm-Fuqs.d.mts} +65 -3
- package/dist/editable-text-Dvm-Fuqs.d.mts.map +1 -0
- package/dist/events/index.d.mts +52 -31
- package/dist/events/index.d.mts.map +1 -1
- package/dist/events/index.mjs +40 -28
- package/dist/events/index.mjs.map +1 -1
- package/dist/{follow-tab-stop-BDxyoUPQ.mjs → follow-tab-stop-Ci1tqAIW.mjs} +63 -61
- package/dist/follow-tab-stop-Ci1tqAIW.mjs.map +1 -0
- package/dist/{index-Cppf-lBx.d.mts → index-BC8kcar9.d.mts} +35 -16
- package/dist/index-BC8kcar9.d.mts.map +1 -0
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +5 -5
- package/dist/{input-modality-m6v7__Xs.mjs → input-modality-BXD383jg.mjs} +9 -9
- package/dist/input-modality-BXD383jg.mjs.map +1 -0
- package/dist/{input-modality-DGOhAknO.d.mts → input-modality-BY-bSrEc.d.mts} +9 -2
- package/dist/input-modality-BY-bSrEc.d.mts.map +1 -0
- package/dist/interaction/index.d.mts +118 -118
- package/dist/interaction/index.d.mts.map +1 -1
- package/dist/interaction/index.mjs +3 -3
- package/dist/interaction/index.mjs.map +1 -1
- package/dist/media/index.d.mts +24 -3
- package/dist/media/index.d.mts.map +1 -1
- package/dist/media/index.mjs +23 -13
- package/dist/media/index.mjs.map +1 -1
- package/dist/motion/index.d.mts +1 -1
- package/dist/motion/index.mjs +1 -2
- package/dist/{motion-DGGBZ0wt.mjs → motion-DmFDppRn.mjs} +20 -20
- package/dist/{motion-DGGBZ0wt.mjs.map → motion-DmFDppRn.mjs.map} +1 -1
- package/dist/net/index.d.mts +57 -19
- package/dist/net/index.d.mts.map +1 -1
- package/dist/net/index.mjs +31 -31
- package/dist/net/index.mjs.map +1 -1
- package/dist/observers/index.d.mts +25 -0
- package/dist/observers/index.d.mts.map +1 -1
- package/dist/observers/index.mjs +22 -22
- package/dist/observers/index.mjs.map +1 -1
- package/dist/state/index.d.mts +98 -44
- package/dist/state/index.d.mts.map +1 -1
- package/dist/state/index.mjs +39 -39
- package/dist/state/index.mjs.map +1 -1
- package/dist/time/index.d.mts +35 -5
- package/dist/time/index.d.mts.map +1 -1
- package/dist/time/index.mjs +20 -20
- package/dist/time/index.mjs.map +1 -1
- package/package.json +14 -14
- package/dist/editable-text-BbsPOATY.d.mts.map +0 -1
- package/dist/follow-tab-stop-BDxyoUPQ.mjs.map +0 -1
- package/dist/index-Cppf-lBx.d.mts.map +0 -1
- package/dist/input-modality-DGOhAknO.d.mts.map +0 -1
- package/dist/input-modality-m6v7__Xs.mjs.map +0 -1
package/dist/events/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as unlessSame } from "../internal-DP-xLKbT.mjs";
|
|
2
|
-
import { a as modalityOfKey, i as Modality, n as InputModalityMessage, r as InputModalityModel, t as InputModality } from "../input-modality-
|
|
2
|
+
import { a as modalityOfKey, i as Modality, n as InputModalityMessage, r as InputModalityModel, t as InputModality } from "../input-modality-BXD383jg.mjs";
|
|
3
3
|
import { Effect, Queue, Schema, Stream } from "effect";
|
|
4
4
|
import { defineMessageUnion } from "foldkit/message";
|
|
5
5
|
import * as Subscription from "foldkit/subscription";
|
|
@@ -17,7 +17,7 @@ const ActiveElementMessage = defineMessageUnion({ Changed: {
|
|
|
17
17
|
} });
|
|
18
18
|
/**
|
|
19
19
|
* Focus changes on the document. Without a document the stream is empty
|
|
20
|
-
* instead of throwing. Lift with `Subscription.
|
|
20
|
+
* instead of throwing. Lift with `Subscription.persistentEntry`, mapping into
|
|
21
21
|
* the parent's Message.
|
|
22
22
|
*/
|
|
23
23
|
const activeElementEvents = () => {
|
|
@@ -55,6 +55,18 @@ const keyNameOf = (part) => {
|
|
|
55
55
|
default: return part;
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
|
+
/**
|
|
59
|
+
* Hotkey matching as a pure function over a `Pressed` payload: `matchHotkey`
|
|
60
|
+
* answers whether a press is the shortcut, so `update` stays a table of
|
|
61
|
+
* chords instead of a nest of modifier checks. Patterns read like
|
|
62
|
+
* `"ctrl+shift+k"`: modifiers first (any order), the key last,
|
|
63
|
+
* case-insensitive, with `cmd`/`command` and `option` as the Mac names for
|
|
64
|
+
* meta and alt. Matching is exact — unlisted modifiers must be up — and
|
|
65
|
+
* auto-repeat never matches, so holding a chord fires once. `space` (or a
|
|
66
|
+
* bare space) means the space key; `esc` means escape. A pattern with
|
|
67
|
+
* no key, or two keys, matches nothing: predicates return false, they do
|
|
68
|
+
* not throw on the hot path.
|
|
69
|
+
*/
|
|
58
70
|
const matchHotkey = (pattern, press) => {
|
|
59
71
|
if (press.repeat) return false;
|
|
60
72
|
const trimmed = pattern.split("+").map((part) => part.trim().toLowerCase()).filter((part) => part !== "");
|
|
@@ -76,14 +88,6 @@ const matchHotkey = (pattern, press) => {
|
|
|
76
88
|
};
|
|
77
89
|
//#endregion
|
|
78
90
|
//#region src/events/idle.ts
|
|
79
|
-
/**
|
|
80
|
-
* Idleness as a bundle: `idle` in the Model, flipped by activity and
|
|
81
|
-
* silence. While active, activity debounced past `timeoutMs` settles to
|
|
82
|
-
* `BecameIdle`; while idle, the first activity wakes to `BecameActive` and
|
|
83
|
-
* the dependency flip restarts the watch. Starts active — a lurker idles
|
|
84
|
-
* when the timeout elapses. The clock is Effect's, so tests drive it with
|
|
85
|
-
* TestClock.
|
|
86
|
-
*/
|
|
87
91
|
const IdleModel = Schema.Struct({ idle: Schema.Boolean });
|
|
88
92
|
const IdleMessage = defineMessageUnion({
|
|
89
93
|
BecameIdle: {},
|
|
@@ -99,6 +103,14 @@ const activity = () => {
|
|
|
99
103
|
];
|
|
100
104
|
return Stream.mergeAll(senses, { concurrency: "unbounded" });
|
|
101
105
|
};
|
|
106
|
+
/**
|
|
107
|
+
* Idleness as a bundle: `idle` in the Model, flipped by activity and
|
|
108
|
+
* silence. While active, activity debounced past `timeoutMs` settles to
|
|
109
|
+
* `BecameIdle`; while idle, the first activity wakes to `BecameActive` and
|
|
110
|
+
* the dependency flip restarts the watch. Starts active — a lurker idles
|
|
111
|
+
* when the timeout elapses. The clock is Effect's, so tests drive it with
|
|
112
|
+
* TestClock.
|
|
113
|
+
*/
|
|
102
114
|
const Idle = Bundle.make("Idle", {
|
|
103
115
|
Model: IdleModel,
|
|
104
116
|
Message: IdleMessage,
|
|
@@ -140,7 +152,7 @@ const KeyboardMessage = defineMessageUnion({
|
|
|
140
152
|
});
|
|
141
153
|
/**
|
|
142
154
|
* Key presses and releases on the window. Without a window the stream is
|
|
143
|
-
* empty instead of throwing. Lift with `Subscription.
|
|
155
|
+
* empty instead of throwing. Lift with `Subscription.persistentEntry`, mapping
|
|
144
156
|
* into the parent's Message.
|
|
145
157
|
*/
|
|
146
158
|
const keyboardEvents = (options = {}) => {
|
|
@@ -184,7 +196,7 @@ const PointerMessage = defineMessageUnion({ Moved: {
|
|
|
184
196
|
} });
|
|
185
197
|
/**
|
|
186
198
|
* Pointer moves on the window. Without a window the stream is empty instead
|
|
187
|
-
* of throwing. Lift with `Subscription.
|
|
199
|
+
* of throwing. Lift with `Subscription.persistentEntry`, mapping into the
|
|
188
200
|
* parent's Message.
|
|
189
201
|
*/
|
|
190
202
|
const pointerEvents = () => {
|
|
@@ -209,7 +221,7 @@ const ScrollMessage = defineMessageUnion({ Scrolled: {
|
|
|
209
221
|
/**
|
|
210
222
|
* Scrolls on the window, in the capture phase so container scrolls (which
|
|
211
223
|
* do not bubble) report too. Without a window the stream is empty instead
|
|
212
|
-
* of throwing. Lift with `Subscription.
|
|
224
|
+
* of throwing. Lift with `Subscription.persistentEntry`, mapping into the
|
|
213
225
|
* parent's Message.
|
|
214
226
|
*/
|
|
215
227
|
const scrollEvents = () => {
|
|
@@ -221,12 +233,6 @@ const scrollEvents = () => {
|
|
|
221
233
|
};
|
|
222
234
|
//#endregion
|
|
223
235
|
//#region src/events/visibility.ts
|
|
224
|
-
/**
|
|
225
|
-
* Page visibility as a bundle: `visible` in the Model, read from the
|
|
226
|
-
* document at startup and kept current by `visibilitychange`. A hidden page
|
|
227
|
-
* is the application's cue to pause polling, animations, and realtime
|
|
228
|
-
* streams; the decision stays in application `update`, not here.
|
|
229
|
-
*/
|
|
230
236
|
const VisibilityModel = Schema.Struct({ visible: Schema.Boolean });
|
|
231
237
|
const VisibilityMessage = defineMessageUnion({ Changed: { visible: Schema.Boolean } });
|
|
232
238
|
const isVisible = () => typeof document === "undefined" || document.visibilityState !== "hidden";
|
|
@@ -234,22 +240,21 @@ const visibilityStream = () => {
|
|
|
234
240
|
if (typeof document === "undefined" || typeof document.addEventListener !== "function") return Stream.empty;
|
|
235
241
|
return Stream.concat(Stream.make(VisibilityMessage.Changed({ visible: isVisible() })), Stream.fromEventListener(document, "visibilitychange").pipe(Stream.map(() => VisibilityMessage.Changed({ visible: isVisible() }))));
|
|
236
242
|
};
|
|
243
|
+
/**
|
|
244
|
+
* Page visibility as a bundle: `visible` in the Model, read from the
|
|
245
|
+
* document at startup and kept current by `visibilitychange`. A hidden page
|
|
246
|
+
* is the application's cue to pause polling, animations, and realtime
|
|
247
|
+
* streams; the decision stays in application `update`, not here.
|
|
248
|
+
*/
|
|
237
249
|
const Visibility = Bundle.make("Visibility", {
|
|
238
250
|
Model: VisibilityModel,
|
|
239
251
|
Message: VisibilityMessage,
|
|
240
252
|
init: () => ({ model: { visible: isVisible() } }),
|
|
241
253
|
update: (model, message) => message.visible === model.visible ? { model } : { model: { visible: message.visible } },
|
|
242
|
-
subscriptions: () => Subscription.make()(() => ({ changes: Subscription.
|
|
254
|
+
subscriptions: () => Subscription.make()(() => ({ changes: Subscription.persistentEntry(visibilityStream()) }))
|
|
243
255
|
});
|
|
244
256
|
//#endregion
|
|
245
257
|
//#region src/events/window-size.ts
|
|
246
|
-
/**
|
|
247
|
-
* Window size as a bundle: `{ width, height }` in the Model, read from the
|
|
248
|
-
* window at startup and kept current by one `resize` listener. Breakpoints
|
|
249
|
-
* owns its own listener and derives names; place this one when views need
|
|
250
|
-
* the raw pixels. Placing both doubles resize listeners — pick the one the
|
|
251
|
-
* view reads.
|
|
252
|
-
*/
|
|
253
258
|
const WindowSizeModel = Schema.Struct({
|
|
254
259
|
width: Schema.Number,
|
|
255
260
|
height: Schema.Number
|
|
@@ -284,6 +289,13 @@ const sizeStream = () => {
|
|
|
284
289
|
})), Stream.fromQueue(queue));
|
|
285
290
|
}));
|
|
286
291
|
};
|
|
292
|
+
/**
|
|
293
|
+
* Window size as a bundle: `{ width, height }` in the Model, read from the
|
|
294
|
+
* window at startup and kept current by one `resize` listener. Breakpoints
|
|
295
|
+
* owns its own listener and derives names; place this one when views need
|
|
296
|
+
* the raw pixels. Placing both doubles resize listeners — pick the one the
|
|
297
|
+
* view reads.
|
|
298
|
+
*/
|
|
287
299
|
const WindowSize = Bundle.make("WindowSize", {
|
|
288
300
|
Model: WindowSizeModel,
|
|
289
301
|
Message: WindowSizeMessage,
|
|
@@ -295,7 +307,7 @@ const WindowSize = Bundle.make("WindowSize", {
|
|
|
295
307
|
width: message.width,
|
|
296
308
|
height: message.height
|
|
297
309
|
}) }),
|
|
298
|
-
subscriptions: () => Subscription.make()(() => ({ changes: Subscription.
|
|
310
|
+
subscriptions: () => Subscription.make()(() => ({ changes: Subscription.persistentEntry(sizeStream()) }))
|
|
299
311
|
});
|
|
300
312
|
//#endregion
|
|
301
313
|
export { ActiveElementMessage, Idle, IdleMessage, IdleModel, InputModality, InputModalityMessage, InputModalityModel, KeyboardMessage, Modality, PointerMessage, ScrollMessage, Visibility, VisibilityMessage, VisibilityModel, WindowSize, WindowSizeMessage, WindowSizeModel, activeElementEvents, keyboardEvents, matchHotkey, modalityOfKey, pointerEvents, scrollEvents };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/events/active-element.ts","../../src/events/hotkeys.ts","../../src/events/idle.ts","../../src/events/keyboard.ts","../../src/events/pointer.ts","../../src/events/scroll.ts","../../src/events/visibility.ts","../../src/events/window-size.ts"],"sourcesContent":["/**\n * Focused element as an entry — not a bundle, because elements are not\n * Model facts: this reports the active element's tag and id (both\n * serializable), and the parent stores what matters. Starts with the\n * current answer, then follows `focusin`/`focusout`.\n */\nimport { Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\n\nexport const ActiveElementMessage = defineMessageUnion({\n Changed: { tag: Schema.String, id: Schema.NullOr(Schema.String) },\n})\nexport type ActiveElementMessage = typeof ActiveElementMessage.Type\n\n/**\n * Focus changes on the document. Without a document the stream is empty\n * instead of throwing. Lift with `Subscription.persistent`, mapping into\n * the parent's Message.\n */\nexport const activeElementEvents = (): Stream.Stream<ActiveElementMessage> => {\n if (typeof document === 'undefined') return Stream.empty\n const read = (): ActiveElementMessage => {\n const element = document.activeElement as Element | null | undefined\n return ActiveElementMessage.Changed({\n tag: element?.tagName ?? '',\n id: element?.id !== undefined && element.id !== '' ? element.id : null,\n })\n }\n const changes: Stream.Stream<ActiveElementMessage> = Stream.mergeAll(\n [\n Stream.fromEventListener(document, 'focusin').pipe(Stream.map(read)),\n Stream.fromEventListener(document, 'focusout').pipe(Stream.map(read)),\n ],\n { concurrency: 'unbounded' },\n )\n return Stream.concat(Stream.make(read()), changes)\n}\n","/**\n * Hotkey matching as a pure function over a `Pressed` payload: `matchHotkey`\n * answers whether a press is the shortcut, so `update` stays a table of\n * chords instead of a nest of modifier checks. Patterns read like\n * `\"ctrl+shift+k\"`: modifiers first (any order), the key last,\n * case-insensitive, with `cmd`/`command` and `option` as the Mac names for\n * meta and alt. Matching is exact — unlisted modifiers must be up — and\n * auto-repeat never matches, so holding a chord fires once. `space` (or a\n * bare space) means the space key; `esc` means escape. A pattern with\n * no key, or two keys, matches nothing: predicates return false, they do\n * not throw on the hot path.\n */\n\nexport interface KeyPress {\n readonly key: string\n readonly repeat: boolean\n readonly ctrl: boolean\n readonly shift: boolean\n readonly alt: boolean\n readonly meta: boolean\n}\n\nconst modifierOf = (part: string): 'ctrl' | 'shift' | 'alt' | 'meta' | null => {\n switch (part) {\n case 'ctrl':\n case 'control':\n return 'ctrl'\n case 'shift':\n return 'shift'\n case 'alt':\n case 'option':\n return 'alt'\n case 'meta':\n case 'cmd':\n case 'command':\n return 'meta'\n default:\n return null\n }\n}\n\nconst keyNameOf = (part: string): string => {\n switch (part) {\n case 'spacebar':\n case ' ':\n return 'space'\n case 'esc':\n return 'escape'\n default:\n return part\n }\n}\n\nexport const matchHotkey = (pattern: string, press: KeyPress): boolean => {\n if (press.repeat) return false\n const trimmed = pattern\n .split('+')\n .map(part => part.trim().toLowerCase())\n .filter(part => part !== '')\n // A bare whitespace pattern means the space key; an empty pattern is malformed.\n const parts = trimmed.length === 0 && pattern.length > 0 ? ['space'] : trimmed\n const keys = parts.filter(part => modifierOf(part) === null)\n if (keys.length !== 1) return false\n if (keyNameOf(keys[0]!) !== keyNameOf(press.key.toLowerCase())) return false\n const required: Record<'ctrl' | 'shift' | 'alt' | 'meta', boolean> = {\n ctrl: false,\n shift: false,\n alt: false,\n meta: false,\n }\n for (const part of parts) {\n const modifier = modifierOf(part)\n if (modifier !== null) required[modifier] = true\n }\n return (\n required.ctrl === press.ctrl &&\n required.shift === press.shift &&\n required.alt === press.alt &&\n required.meta === press.meta\n )\n}\n","/**\n * Idleness as a bundle: `idle` in the Model, flipped by activity and\n * silence. While active, activity debounced past `timeoutMs` settles to\n * `BecameIdle`; while idle, the first activity wakes to `BecameActive` and\n * the dependency flip restarts the watch. Starts active — a lurker idles\n * when the timeout elapses. The clock is Effect's, so tests drive it with\n * TestClock.\n */\nimport { Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const IdleModel = Schema.Struct({ idle: Schema.Boolean })\nexport type IdleModel = typeof IdleModel.Type\n\nexport const IdleMessage = defineMessageUnion({ BecameIdle: {}, BecameActive: {} })\nexport type IdleMessage = typeof IdleMessage.Type\n\nconst activity = (): Stream.Stream<unknown> => {\n if (typeof window === 'undefined') return Stream.empty\n // One stream per sense; the merge fans into the debounce below.\n const senses: Array<Stream.Stream<unknown>> = [\n Stream.fromEventListener(window, 'mousemove'),\n Stream.fromEventListener(window, 'keydown'),\n Stream.fromEventListener(window, 'pointerdown'),\n Stream.fromEventListener(window, 'scroll'),\n ]\n return Stream.mergeAll(senses, { concurrency: 'unbounded' })\n}\n\nexport const Idle = Bundle.make('Idle', {\n Model: IdleModel,\n Message: IdleMessage,\n args: Schema.Struct({\n timeoutMs: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n }),\n init: () => ({ model: { idle: false } }),\n update: (model, message) =>\n IdleMessage.match<Update.ReturnWithOutMessage<IdleModel, IdleMessage, never>>(message, {\n // One already in flight when the watch restarts can repeat the state.\n BecameIdle: () => (model.idle ? { model } : { model: { ...model, idle: true } }),\n BecameActive: () => (model.idle ? { model: { ...model, idle: false } } : { model }),\n }),\n subscriptions: ({ timeoutMs }): Subscription.Subscriptions<IdleModel, IdleMessage> =>\n Subscription.make<IdleModel, IdleMessage>()(entry => ({\n watch: entry(\n { idle: Schema.Boolean },\n {\n modelToDependencies: model => ({ idle: model.idle }),\n dependenciesToStream: ({ idle }) =>\n idle\n ? activity().pipe(\n Stream.take(1),\n Stream.map(() => IdleMessage.BecameActive()),\n )\n : // Seeded with subscribe time as last-known-alive: silence from\n // here still settles, so a lurker idles without moving first.\n Stream.concat(Stream.make(null), activity()).pipe(\n Stream.debounce(timeoutMs),\n Stream.map(() => IdleMessage.BecameIdle()),\n ),\n },\n ),\n })),\n})\n","/**\n * Keyboard as an entry — not a bundle, because the parent owns whatever key\n * state it keeps (a held-keys set, a shortcut map, a focused field): this\n * reports presses and releases, including auto-repeat, and the parent\n * stores what matters. Hotkey matching stays application policy.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport type { KeyPress } from './hotkeys.js'\n\nexport const KeyboardMessage = defineMessageUnion({\n Pressed: {\n key: Schema.String,\n repeat: Schema.Boolean,\n ctrl: Schema.Boolean,\n shift: Schema.Boolean,\n alt: Schema.Boolean,\n meta: Schema.Boolean,\n },\n Released: { key: Schema.String },\n})\nexport type KeyboardMessage = typeof KeyboardMessage.Type\n\n/**\n * Key presses and releases on the window. Without a window the stream is\n * empty instead of throwing. Lift with `Subscription.persistent`, mapping\n * into the parent's Message.\n */\nexport const keyboardEvents = (\n options: {\n /**\n * Whether to cancel a press's default (the page scrolling under an arrow\n * key, the browser's own shortcut), decided in the listener, since a\n * default can only be cancelled before the event finishes dispatching.\n */\n readonly preventDefault?: (press: KeyPress) => boolean\n } = {},\n): Stream.Stream<KeyboardMessage> => {\n if (typeof window === 'undefined') return Stream.empty\n return Stream.callback<KeyboardMessage>(queue =>\n Effect.acquireRelease(\n Effect.sync(() => {\n const down = (event: KeyboardEvent) => {\n const press = KeyboardMessage.Pressed({\n key: event.key,\n repeat: event.repeat,\n ctrl: event.ctrlKey,\n shift: event.shiftKey,\n alt: event.altKey,\n meta: event.metaKey,\n })\n if (options.preventDefault?.(press) === true) event.preventDefault()\n Queue.offerUnsafe(queue, press)\n }\n const up = (event: KeyboardEvent) =>\n Queue.offerUnsafe(queue, KeyboardMessage.Released({ key: event.key }))\n window.addEventListener('keydown', down)\n window.addEventListener('keyup', up)\n return { down, up }\n }),\n ({ down, up }) =>\n Effect.sync(() => {\n window.removeEventListener('keydown', down)\n window.removeEventListener('keyup', up)\n }),\n ),\n )\n}\n","/**\n * Pointer position as an entry — not a bundle, because the parent owns what\n * it keeps (a cursor, a drag anchor, hover state): this reports moves, and\n * the parent stores what matters. Element-scoped pointer needs are Mounts,\n * not this window-level entry.\n */\nimport { Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\n\nexport const PointerMessage = defineMessageUnion({\n Moved: { x: Schema.Number, y: Schema.Number },\n})\nexport type PointerMessage = typeof PointerMessage.Type\n\n/**\n * Pointer moves on the window. Without a window the stream is empty instead\n * of throwing. Lift with `Subscription.persistent`, mapping into the\n * parent's Message.\n */\nexport const pointerEvents = (): Stream.Stream<PointerMessage> => {\n if (typeof window === 'undefined') return Stream.empty\n return Stream.fromEventListener<PointerEvent>(window, 'pointermove').pipe(\n Stream.map(event => PointerMessage.Moved({ x: event.clientX, y: event.clientY })),\n )\n}\n","/**\n * Window scroll position as an entry — not a bundle, because the parent\n * owns what it keeps (a stuck header, a back-to-top gate, restore-on-back):\n * this reports positions, and the parent stores what matters.\n * Element-scoped scrolling needs a Mount, not this window-level entry.\n */\nimport { Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\n\nexport const ScrollMessage = defineMessageUnion({\n Scrolled: { x: Schema.Number, y: Schema.Number },\n})\nexport type ScrollMessage = typeof ScrollMessage.Type\n\n/**\n * Scrolls on the window, in the capture phase so container scrolls (which\n * do not bubble) report too. Without a window the stream is empty instead\n * of throwing. Lift with `Subscription.persistent`, mapping into the\n * parent's Message.\n */\nexport const scrollEvents = (): Stream.Stream<ScrollMessage> => {\n if (typeof window === 'undefined') return Stream.empty\n return Stream.fromEventListener(window, 'scroll', { capture: true }).pipe(\n Stream.map(() => ScrollMessage.Scrolled({ x: window.scrollX, y: window.scrollY })),\n )\n}\n","/**\n * Page visibility as a bundle: `visible` in the Model, read from the\n * document at startup and kept current by `visibilitychange`. A hidden page\n * is the application's cue to pause polling, animations, and realtime\n * streams; the decision stays in application `update`, not here.\n */\nimport { Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const VisibilityModel = Schema.Struct({ visible: Schema.Boolean })\nexport type VisibilityModel = typeof VisibilityModel.Type\n\nexport const VisibilityMessage = defineMessageUnion({ Changed: { visible: Schema.Boolean } })\nexport type VisibilityMessage = typeof VisibilityMessage.Type\n\nconst isVisible = (): boolean =>\n typeof document === 'undefined' || document.visibilityState !== 'hidden'\n\nconst visibilityStream = (): Stream.Stream<VisibilityMessage> => {\n if (typeof document === 'undefined' || typeof document.addEventListener !== 'function') {\n return Stream.empty\n }\n return Stream.concat(\n Stream.make(VisibilityMessage.Changed({ visible: isVisible() })),\n Stream.fromEventListener(document, 'visibilitychange').pipe(\n Stream.map(() => VisibilityMessage.Changed({ visible: isVisible() })),\n ),\n )\n}\n\nexport const Visibility = Bundle.make('Visibility', {\n Model: VisibilityModel,\n Message: VisibilityMessage,\n // No document (SSR): assume visible; the stream corrects on subscribe.\n init: () => ({ model: { visible: isVisible() } }),\n // The stream opens with the state `init` already read.\n update: (model, message) =>\n message.visible === model.visible ? { model } : { model: { visible: message.visible } },\n subscriptions: (): Subscription.Subscriptions<VisibilityModel, VisibilityMessage> =>\n Subscription.make<VisibilityModel, VisibilityMessage>()(() => ({\n changes: Subscription.persistent(visibilityStream()),\n })),\n})\n","/**\n * Window size as a bundle: `{ width, height }` in the Model, read from the\n * window at startup and kept current by one `resize` listener. Breakpoints\n * owns its own listener and derives names; place this one when views need\n * the raw pixels. Placing both doubles resize listeners — pick the one the\n * view reads.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\nimport { unlessSame } from '../internal.js'\n\nexport const WindowSizeModel = Schema.Struct({ width: Schema.Number, height: Schema.Number })\nexport type WindowSizeModel = typeof WindowSizeModel.Type\n\nexport const WindowSizeMessage = defineMessageUnion({\n Changed: { width: Schema.Number, height: Schema.Number },\n})\nexport type WindowSizeMessage = typeof WindowSizeMessage.Type\n\nconst readSize = (): { readonly width: number; readonly height: number } => ({\n width: window.innerWidth,\n height: window.innerHeight,\n})\n\nconst sizeStream = (): Stream.Stream<WindowSizeMessage> => {\n if (typeof window === 'undefined' || typeof window.addEventListener !== 'function') {\n return Stream.empty\n }\n return Stream.unwrap(\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<WindowSizeMessage>()\n const onResize = () => {\n const size = readSize()\n Effect.runFork(\n Queue.offer(queue, WindowSizeMessage.Changed({ width: size.width, height: size.height })),\n )\n }\n window.addEventListener('resize', onResize)\n yield* Effect.addFinalizer(() =>\n Effect.sync(() => {\n window.removeEventListener('resize', onResize)\n }),\n )\n const size = readSize()\n return Stream.concat(\n Stream.make(WindowSizeMessage.Changed({ width: size.width, height: size.height })),\n Stream.fromQueue(queue),\n )\n }),\n )\n}\n\nexport const WindowSize = Bundle.make('WindowSize', {\n Model: WindowSizeModel,\n Message: WindowSizeMessage,\n init: () => ({ model: { width: 0, height: 0 } }),\n update: (model, message) => ({\n model: unlessSame(model, { width: message.width, height: message.height }),\n }),\n subscriptions: (): Subscription.Subscriptions<WindowSizeModel, WindowSizeMessage> =>\n Subscription.make<WindowSizeModel, WindowSizeMessage>()(() => ({\n changes: Subscription.persistent(sizeStream()),\n })),\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/events/active-element.ts","../../src/events/hotkeys.ts","../../src/events/idle.ts","../../src/events/keyboard.ts","../../src/events/pointer.ts","../../src/events/scroll.ts","../../src/events/visibility.ts","../../src/events/window-size.ts"],"sourcesContent":["/**\n * Focused element as an entry — not a bundle, because elements are not\n * Model facts: this reports the active element's tag and id (both\n * serializable), and the parent stores what matters. Starts with the\n * current answer, then follows `focusin`/`focusout`.\n */\nimport { Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\n\nexport const ActiveElementMessage = defineMessageUnion({\n Changed: { tag: Schema.String, id: Schema.NullOr(Schema.String) },\n})\nexport type ActiveElementMessage = typeof ActiveElementMessage.Type\n\n/**\n * Focus changes on the document. Without a document the stream is empty\n * instead of throwing. Lift with `Subscription.persistentEntry`, mapping into\n * the parent's Message.\n */\nexport const activeElementEvents = (): Stream.Stream<ActiveElementMessage> => {\n if (typeof document === 'undefined') return Stream.empty\n const read = (): ActiveElementMessage => {\n const element = document.activeElement as Element | null | undefined\n return ActiveElementMessage.Changed({\n tag: element?.tagName ?? '',\n id: element?.id !== undefined && element.id !== '' ? element.id : null,\n })\n }\n const changes: Stream.Stream<ActiveElementMessage> = Stream.mergeAll(\n [\n Stream.fromEventListener(document, 'focusin').pipe(Stream.map(read)),\n Stream.fromEventListener(document, 'focusout').pipe(Stream.map(read)),\n ],\n { concurrency: 'unbounded' },\n )\n return Stream.concat(Stream.make(read()), changes)\n}\n","export interface KeyPress {\n readonly key: string\n readonly repeat: boolean\n readonly ctrl: boolean\n readonly shift: boolean\n readonly alt: boolean\n readonly meta: boolean\n}\n\nconst modifierOf = (part: string): 'ctrl' | 'shift' | 'alt' | 'meta' | null => {\n switch (part) {\n case 'ctrl':\n case 'control':\n return 'ctrl'\n case 'shift':\n return 'shift'\n case 'alt':\n case 'option':\n return 'alt'\n case 'meta':\n case 'cmd':\n case 'command':\n return 'meta'\n default:\n return null\n }\n}\n\nconst keyNameOf = (part: string): string => {\n switch (part) {\n case 'spacebar':\n case ' ':\n return 'space'\n case 'esc':\n return 'escape'\n default:\n return part\n }\n}\n\n/**\n * Hotkey matching as a pure function over a `Pressed` payload: `matchHotkey`\n * answers whether a press is the shortcut, so `update` stays a table of\n * chords instead of a nest of modifier checks. Patterns read like\n * `\"ctrl+shift+k\"`: modifiers first (any order), the key last,\n * case-insensitive, with `cmd`/`command` and `option` as the Mac names for\n * meta and alt. Matching is exact — unlisted modifiers must be up — and\n * auto-repeat never matches, so holding a chord fires once. `space` (or a\n * bare space) means the space key; `esc` means escape. A pattern with\n * no key, or two keys, matches nothing: predicates return false, they do\n * not throw on the hot path.\n */\nexport const matchHotkey = (pattern: string, press: KeyPress): boolean => {\n if (press.repeat) return false\n const trimmed = pattern\n .split('+')\n .map(part => part.trim().toLowerCase())\n .filter(part => part !== '')\n // A bare whitespace pattern means the space key; an empty pattern is malformed.\n const parts = trimmed.length === 0 && pattern.length > 0 ? ['space'] : trimmed\n const keys = parts.filter(part => modifierOf(part) === null)\n if (keys.length !== 1) return false\n if (keyNameOf(keys[0]!) !== keyNameOf(press.key.toLowerCase())) return false\n const required: Record<'ctrl' | 'shift' | 'alt' | 'meta', boolean> = {\n ctrl: false,\n shift: false,\n alt: false,\n meta: false,\n }\n for (const part of parts) {\n const modifier = modifierOf(part)\n if (modifier !== null) required[modifier] = true\n }\n return (\n required.ctrl === press.ctrl &&\n required.shift === press.shift &&\n required.alt === press.alt &&\n required.meta === press.meta\n )\n}\n","import { Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const IdleModel = Schema.Struct({ idle: Schema.Boolean })\nexport type IdleModel = typeof IdleModel.Type\n\nexport const IdleMessage = defineMessageUnion({ BecameIdle: {}, BecameActive: {} })\nexport type IdleMessage = typeof IdleMessage.Type\n\nconst activity = (): Stream.Stream<unknown> => {\n if (typeof window === 'undefined') return Stream.empty\n // One stream per sense; the merge fans into the debounce below.\n const senses: Array<Stream.Stream<unknown>> = [\n Stream.fromEventListener(window, 'mousemove'),\n Stream.fromEventListener(window, 'keydown'),\n Stream.fromEventListener(window, 'pointerdown'),\n Stream.fromEventListener(window, 'scroll'),\n ]\n return Stream.mergeAll(senses, { concurrency: 'unbounded' })\n}\n\n/**\n * Idleness as a bundle: `idle` in the Model, flipped by activity and\n * silence. While active, activity debounced past `timeoutMs` settles to\n * `BecameIdle`; while idle, the first activity wakes to `BecameActive` and\n * the dependency flip restarts the watch. Starts active — a lurker idles\n * when the timeout elapses. The clock is Effect's, so tests drive it with\n * TestClock.\n */\nexport const Idle = Bundle.make('Idle', {\n Model: IdleModel,\n Message: IdleMessage,\n args: Schema.Struct({\n timeoutMs: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n }),\n init: () => ({ model: { idle: false } }),\n update: (model, message) =>\n IdleMessage.match<Update.ReturnWithOutMessage<IdleModel, IdleMessage, never>>(message, {\n // One already in flight when the watch restarts can repeat the state.\n BecameIdle: () => (model.idle ? { model } : { model: { ...model, idle: true } }),\n BecameActive: () => (model.idle ? { model: { ...model, idle: false } } : { model }),\n }),\n subscriptions: ({ timeoutMs }): Subscription.Subscriptions<IdleModel, IdleMessage> =>\n Subscription.make<IdleModel, IdleMessage>()(entry => ({\n watch: entry(\n { idle: Schema.Boolean },\n {\n modelToDependencies: model => ({ idle: model.idle }),\n dependenciesToStream: ({ idle }) =>\n idle\n ? activity().pipe(\n Stream.take(1),\n Stream.map(() => IdleMessage.BecameActive()),\n )\n : // Seeded with subscribe time as last-known-alive: silence from\n // here still settles, so a lurker idles without moving first.\n Stream.concat(Stream.make(null), activity()).pipe(\n Stream.debounce(timeoutMs),\n Stream.map(() => IdleMessage.BecameIdle()),\n ),\n },\n ),\n })),\n})\n","/**\n * Keyboard as an entry — not a bundle, because the parent owns whatever key\n * state it keeps (a held-keys set, a shortcut map, a focused field): this\n * reports presses and releases, including auto-repeat, and the parent\n * stores what matters. Hotkey matching stays application policy.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport type { KeyPress } from './hotkeys.js'\n\nexport const KeyboardMessage = defineMessageUnion({\n Pressed: {\n key: Schema.String,\n repeat: Schema.Boolean,\n ctrl: Schema.Boolean,\n shift: Schema.Boolean,\n alt: Schema.Boolean,\n meta: Schema.Boolean,\n },\n Released: { key: Schema.String },\n})\nexport type KeyboardMessage = typeof KeyboardMessage.Type\n\n/**\n * Key presses and releases on the window. Without a window the stream is\n * empty instead of throwing. Lift with `Subscription.persistentEntry`, mapping\n * into the parent's Message.\n */\nexport const keyboardEvents = (\n options: {\n /**\n * Whether to cancel a press's default (the page scrolling under an arrow\n * key, the browser's own shortcut), decided in the listener, since a\n * default can only be cancelled before the event finishes dispatching.\n */\n readonly preventDefault?: (press: KeyPress) => boolean\n } = {},\n): Stream.Stream<KeyboardMessage> => {\n if (typeof window === 'undefined') return Stream.empty\n return Stream.callback<KeyboardMessage>(queue =>\n Effect.acquireRelease(\n Effect.sync(() => {\n const down = (event: KeyboardEvent) => {\n const press = KeyboardMessage.Pressed({\n key: event.key,\n repeat: event.repeat,\n ctrl: event.ctrlKey,\n shift: event.shiftKey,\n alt: event.altKey,\n meta: event.metaKey,\n })\n if (options.preventDefault?.(press) === true) event.preventDefault()\n Queue.offerUnsafe(queue, press)\n }\n const up = (event: KeyboardEvent) =>\n Queue.offerUnsafe(queue, KeyboardMessage.Released({ key: event.key }))\n window.addEventListener('keydown', down)\n window.addEventListener('keyup', up)\n return { down, up }\n }),\n ({ down, up }) =>\n Effect.sync(() => {\n window.removeEventListener('keydown', down)\n window.removeEventListener('keyup', up)\n }),\n ),\n )\n}\n","/**\n * Pointer position as an entry — not a bundle, because the parent owns what\n * it keeps (a cursor, a drag anchor, hover state): this reports moves, and\n * the parent stores what matters. Element-scoped pointer needs are Mounts,\n * not this window-level entry.\n */\nimport { Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\n\nexport const PointerMessage = defineMessageUnion({\n Moved: { x: Schema.Number, y: Schema.Number },\n})\nexport type PointerMessage = typeof PointerMessage.Type\n\n/**\n * Pointer moves on the window. Without a window the stream is empty instead\n * of throwing. Lift with `Subscription.persistentEntry`, mapping into the\n * parent's Message.\n */\nexport const pointerEvents = (): Stream.Stream<PointerMessage> => {\n if (typeof window === 'undefined') return Stream.empty\n return Stream.fromEventListener<PointerEvent>(window, 'pointermove').pipe(\n Stream.map(event => PointerMessage.Moved({ x: event.clientX, y: event.clientY })),\n )\n}\n","/**\n * Window scroll position as an entry — not a bundle, because the parent\n * owns what it keeps (a stuck header, a back-to-top gate, restore-on-back):\n * this reports positions, and the parent stores what matters.\n * Element-scoped scrolling needs a Mount, not this window-level entry.\n */\nimport { Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\n\nexport const ScrollMessage = defineMessageUnion({\n Scrolled: { x: Schema.Number, y: Schema.Number },\n})\nexport type ScrollMessage = typeof ScrollMessage.Type\n\n/**\n * Scrolls on the window, in the capture phase so container scrolls (which\n * do not bubble) report too. Without a window the stream is empty instead\n * of throwing. Lift with `Subscription.persistentEntry`, mapping into the\n * parent's Message.\n */\nexport const scrollEvents = (): Stream.Stream<ScrollMessage> => {\n if (typeof window === 'undefined') return Stream.empty\n return Stream.fromEventListener(window, 'scroll', { capture: true }).pipe(\n Stream.map(() => ScrollMessage.Scrolled({ x: window.scrollX, y: window.scrollY })),\n )\n}\n","import { Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const VisibilityModel = Schema.Struct({ visible: Schema.Boolean })\nexport type VisibilityModel = typeof VisibilityModel.Type\n\nexport const VisibilityMessage = defineMessageUnion({ Changed: { visible: Schema.Boolean } })\nexport type VisibilityMessage = typeof VisibilityMessage.Type\n\nconst isVisible = (): boolean =>\n typeof document === 'undefined' || document.visibilityState !== 'hidden'\n\nconst visibilityStream = (): Stream.Stream<VisibilityMessage> => {\n if (typeof document === 'undefined' || typeof document.addEventListener !== 'function') {\n return Stream.empty\n }\n return Stream.concat(\n Stream.make(VisibilityMessage.Changed({ visible: isVisible() })),\n Stream.fromEventListener(document, 'visibilitychange').pipe(\n Stream.map(() => VisibilityMessage.Changed({ visible: isVisible() })),\n ),\n )\n}\n\n/**\n * Page visibility as a bundle: `visible` in the Model, read from the\n * document at startup and kept current by `visibilitychange`. A hidden page\n * is the application's cue to pause polling, animations, and realtime\n * streams; the decision stays in application `update`, not here.\n */\nexport const Visibility = Bundle.make('Visibility', {\n Model: VisibilityModel,\n Message: VisibilityMessage,\n // No document (SSR): assume visible; the stream corrects on subscribe.\n init: () => ({ model: { visible: isVisible() } }),\n // The stream opens with the state `init` already read.\n update: (model, message) =>\n message.visible === model.visible ? { model } : { model: { visible: message.visible } },\n subscriptions: (): Subscription.Subscriptions<VisibilityModel, VisibilityMessage> =>\n Subscription.make<VisibilityModel, VisibilityMessage>()(() => ({\n changes: Subscription.persistentEntry(visibilityStream()),\n })),\n})\n","import { Effect, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\nimport { unlessSame } from '../internal.js'\n\nexport const WindowSizeModel = Schema.Struct({ width: Schema.Number, height: Schema.Number })\nexport type WindowSizeModel = typeof WindowSizeModel.Type\n\nexport const WindowSizeMessage = defineMessageUnion({\n Changed: { width: Schema.Number, height: Schema.Number },\n})\nexport type WindowSizeMessage = typeof WindowSizeMessage.Type\n\nconst readSize = (): { readonly width: number; readonly height: number } => ({\n width: window.innerWidth,\n height: window.innerHeight,\n})\n\nconst sizeStream = (): Stream.Stream<WindowSizeMessage> => {\n if (typeof window === 'undefined' || typeof window.addEventListener !== 'function') {\n return Stream.empty\n }\n return Stream.unwrap(\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<WindowSizeMessage>()\n const onResize = () => {\n const size = readSize()\n Effect.runFork(\n Queue.offer(queue, WindowSizeMessage.Changed({ width: size.width, height: size.height })),\n )\n }\n window.addEventListener('resize', onResize)\n yield* Effect.addFinalizer(() =>\n Effect.sync(() => {\n window.removeEventListener('resize', onResize)\n }),\n )\n const size = readSize()\n return Stream.concat(\n Stream.make(WindowSizeMessage.Changed({ width: size.width, height: size.height })),\n Stream.fromQueue(queue),\n )\n }),\n )\n}\n\n/**\n * Window size as a bundle: `{ width, height }` in the Model, read from the\n * window at startup and kept current by one `resize` listener. Breakpoints\n * owns its own listener and derives names; place this one when views need\n * the raw pixels. Placing both doubles resize listeners — pick the one the\n * view reads.\n */\nexport const WindowSize = Bundle.make('WindowSize', {\n Model: WindowSizeModel,\n Message: WindowSizeMessage,\n init: () => ({ model: { width: 0, height: 0 } }),\n update: (model, message) => ({\n model: unlessSame(model, { width: message.width, height: message.height }),\n }),\n subscriptions: (): Subscription.Subscriptions<WindowSizeModel, WindowSizeMessage> =>\n Subscription.make<WindowSizeModel, WindowSizeMessage>()(() => ({\n changes: Subscription.persistentEntry(sizeStream()),\n })),\n})\n"],"mappings":"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"}
|
|
@@ -2,13 +2,6 @@ import { Effect, Queue, Schema, Stream } from "effect";
|
|
|
2
2
|
import * as Mount from "foldkit/mount";
|
|
3
3
|
import * as Dom from "foldkit/dom";
|
|
4
4
|
//#region src/dom/focus-scope.ts
|
|
5
|
-
/**
|
|
6
|
-
* A focus scope as a Mount: on insert it remembers what was focused, focuses
|
|
7
|
-
* an initial element inside, and with `contain` keeps Tab and Shift+Tab
|
|
8
|
-
* cycling inside; on unmount it gives focus back. Nothing crosses to the
|
|
9
|
-
* Model: which element had focus is a DOM fact, so this is element lifecycle,
|
|
10
|
-
* not state. It emits no Messages.
|
|
11
|
-
*/
|
|
12
5
|
const FOCUSABLE = [
|
|
13
6
|
"a[href]",
|
|
14
7
|
"button:not([disabled])",
|
|
@@ -30,6 +23,13 @@ const focusFirst = (elements) => {
|
|
|
30
23
|
first.focus();
|
|
31
24
|
return true;
|
|
32
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* A focus scope as a Mount: on insert it remembers what was focused, focuses
|
|
28
|
+
* an initial element inside, and with `contain` keeps Tab and Shift+Tab
|
|
29
|
+
* cycling inside; on unmount it gives focus back. Nothing crosses to the
|
|
30
|
+
* Model: which element had focus is a DOM fact, so this is element lifecycle,
|
|
31
|
+
* not state. It emits no Messages.
|
|
32
|
+
*/
|
|
33
33
|
const FocusScope = Mount.defineStream("FocusScope", {
|
|
34
34
|
messages: [Schema.Never],
|
|
35
35
|
args: {
|
|
@@ -102,12 +102,14 @@ const FocusScope = Mount.defineStream("FocusScope", {
|
|
|
102
102
|
*
|
|
103
103
|
* Both emit no Messages; they are element lifecycle.
|
|
104
104
|
*/
|
|
105
|
+
/** Locks the document's scroll while the element is mounted; nested locks release with the last. */
|
|
105
106
|
const ScrollLock = Mount.defineStream("ScrollLock", {
|
|
106
107
|
messages: [Schema.Never],
|
|
107
108
|
execute: () => Stream.callback(() => Effect.acquireRelease(Dom.lockScroll, () => Dom.unlockScroll))
|
|
108
109
|
});
|
|
109
110
|
const HIDE_OUTSIDE_ATTRIBUTE = "data-foldkit-plus-hide-outside";
|
|
110
111
|
let minted = 0;
|
|
112
|
+
/** Makes everything outside the element inert while it is mounted, and restores it after. */
|
|
111
113
|
const HideOutside = Mount.defineStream("HideOutside", {
|
|
112
114
|
messages: [Schema.Never],
|
|
113
115
|
execute: ({ element }) => Stream.callback(() => Effect.acquireRelease(Effect.gen(function* () {
|
|
@@ -119,13 +121,6 @@ const HideOutside = Mount.defineStream("HideOutside", {
|
|
|
119
121
|
});
|
|
120
122
|
//#endregion
|
|
121
123
|
//#region src/dom/move.ts
|
|
122
|
-
/**
|
|
123
|
-
* Pointer movement on an element as a Mount: a primary-button pointer down
|
|
124
|
-
* captures the pointer, each move reports the delta from where it started,
|
|
125
|
-
* and up, cancel, or lost capture ends it. Nothing is decided here: a drag
|
|
126
|
-
* threshold, a snap, or a constraint is the parent's `update`. Capture is
|
|
127
|
-
* released with the Mount.
|
|
128
|
-
*/
|
|
129
124
|
const MoveStarted = Schema.TaggedStruct("MoveStarted", {
|
|
130
125
|
pointerId: Schema.Number,
|
|
131
126
|
pointerType: Schema.String
|
|
@@ -142,6 +137,13 @@ const MoveEnded = Schema.TaggedStruct("MoveEnded", {
|
|
|
142
137
|
/** `false` when the pointer was cancelled or capture was lost. */
|
|
143
138
|
completed: Schema.Boolean
|
|
144
139
|
});
|
|
140
|
+
/**
|
|
141
|
+
* Pointer movement on an element as a Mount: a primary-button pointer down
|
|
142
|
+
* captures the pointer, each move reports the delta from where it started,
|
|
143
|
+
* and up, cancel, or lost capture ends it. Nothing is decided here: a drag
|
|
144
|
+
* threshold, a snap, or a constraint is the parent's `update`. Capture is
|
|
145
|
+
* released with the Mount.
|
|
146
|
+
*/
|
|
145
147
|
const Move = Mount.defineStream("Move", {
|
|
146
148
|
messages: [
|
|
147
149
|
MoveStarted,
|
|
@@ -211,16 +213,6 @@ const Move = Mount.defineStream("Move", {
|
|
|
211
213
|
});
|
|
212
214
|
//#endregion
|
|
213
215
|
//#region src/dom/targets.ts
|
|
214
|
-
/**
|
|
215
|
-
* Which marked descendant of an element the pointer is over, and which one was
|
|
216
|
-
* pressed, as a Mount: one set of listeners on the container, not one per item.
|
|
217
|
-
* A descendant is marked by an attribute whose value is its id, such as a
|
|
218
|
-
* canvas's `data-composition-node` or a table's `data-row`. The nearest marked
|
|
219
|
-
* ancestor of the event's target, inside the container, is the one reported.
|
|
220
|
-
*
|
|
221
|
-
* What hovering or pressing means (a selection, an outline, a menu) is the
|
|
222
|
-
* parent's `update`.
|
|
223
|
-
*/
|
|
224
216
|
const TargetHovered = Schema.TaggedStruct("TargetHovered", {
|
|
225
217
|
/** The marked descendant under the pointer, or `null` when there is none. */
|
|
226
218
|
id: Schema.NullOr(Schema.String) });
|
|
@@ -237,6 +229,16 @@ const targetOf = (container, from, attribute) => {
|
|
|
237
229
|
const marked = from.closest(`[${attribute}]`);
|
|
238
230
|
return marked !== null && container.contains(marked) ? marked.getAttribute(attribute) : null;
|
|
239
231
|
};
|
|
232
|
+
/**
|
|
233
|
+
* Which marked descendant of an element the pointer is over, and which one was
|
|
234
|
+
* pressed, as a Mount: one set of listeners on the container, not one per item.
|
|
235
|
+
* A descendant is marked by an attribute whose value is its id, such as a
|
|
236
|
+
* canvas's `data-composition-node` or a table's `data-row`. The nearest marked
|
|
237
|
+
* ancestor of the event's target, inside the container, is the one reported.
|
|
238
|
+
*
|
|
239
|
+
* What hovering or pressing means (a selection, an outline, a menu) is the
|
|
240
|
+
* parent's `update`.
|
|
241
|
+
*/
|
|
240
242
|
const Targets = Mount.defineStream("Targets", {
|
|
241
243
|
messages: [TargetHovered, TargetPressed],
|
|
242
244
|
args: {
|
|
@@ -277,20 +279,6 @@ const Targets = Mount.defineStream("Targets", {
|
|
|
277
279
|
});
|
|
278
280
|
//#endregion
|
|
279
281
|
//#region src/dom/pointer-drag.ts
|
|
280
|
-
/**
|
|
281
|
-
* Dragging one marked descendant of an element onto another, as a Mount. A
|
|
282
|
-
* descendant is marked by an attribute whose value is its id, as `Targets`
|
|
283
|
-
* marks them; a press on one that then moves past a threshold starts a drag,
|
|
284
|
-
* and the pointer's place over another marked descendant is reported with the
|
|
285
|
-
* zone of it the pointer is in: its first third (`before`), its middle
|
|
286
|
-
* (`inside`), or its last third (`after`). Releasing drops, and Escape or a
|
|
287
|
-
* cancelled pointer ends the drag with nothing dropped.
|
|
288
|
-
*
|
|
289
|
-
* It writes no roles, no `tabindex` and no key handling, so the marked
|
|
290
|
-
* elements can be anything: a tree's rows, a canvas's nodes. The keyboard's way
|
|
291
|
-
* to do what a drag does is the parent's to give. Geometry is measured when the
|
|
292
|
-
* pointer moves and never kept; what a drop means is the parent's `update`.
|
|
293
|
-
*/
|
|
294
282
|
const DragZone = Schema.Literals([
|
|
295
283
|
"before",
|
|
296
284
|
"inside",
|
|
@@ -344,6 +332,20 @@ const boxOf = (element) => {
|
|
|
344
332
|
const child = element.firstElementChild;
|
|
345
333
|
return child === null ? own : boxOf(child);
|
|
346
334
|
};
|
|
335
|
+
/**
|
|
336
|
+
* Dragging one marked descendant of an element onto another, as a Mount. A
|
|
337
|
+
* descendant is marked by an attribute whose value is its id, as `Targets`
|
|
338
|
+
* marks them; a press on one that then moves past a threshold starts a drag,
|
|
339
|
+
* and the pointer's place over another marked descendant is reported with the
|
|
340
|
+
* zone of it the pointer is in: its first third (`before`), its middle
|
|
341
|
+
* (`inside`), or its last third (`after`). Releasing drops, and Escape or a
|
|
342
|
+
* cancelled pointer ends the drag with nothing dropped.
|
|
343
|
+
*
|
|
344
|
+
* It writes no roles, no `tabindex` and no key handling, so the marked
|
|
345
|
+
* elements can be anything: a tree's rows, a canvas's nodes. The keyboard's way
|
|
346
|
+
* to do what a drag does is the parent's to give. Geometry is measured when the
|
|
347
|
+
* pointer moves and never kept; what a drop means is the parent's `update`.
|
|
348
|
+
*/
|
|
347
349
|
const PointerDrag = Mount.defineStream("PointerDrag", {
|
|
348
350
|
messages: [
|
|
349
351
|
DragStarted,
|
|
@@ -538,28 +540,6 @@ const PointerDrag = Mount.defineStream("PointerDrag", {
|
|
|
538
540
|
});
|
|
539
541
|
//#endregion
|
|
540
542
|
//#region src/dom/editable-text.ts
|
|
541
|
-
/**
|
|
542
|
-
* Text typed into an editable descendant of an element, as a Mount: one set of
|
|
543
|
-
* listeners on the container, like `Targets`. A field is a descendant marked
|
|
544
|
-
* by an attribute whose value names it, while it is `contenteditable`. The
|
|
545
|
-
* view decides which one is; the Mount focuses it as it becomes so (made
|
|
546
|
-
* editable, or drawn editable), with the caret at its end, and at no other
|
|
547
|
-
* time. When Enter or Escape ends an edit and the view then removes the field
|
|
548
|
-
* or makes it no longer editable, focus, left on nothing, comes back to the
|
|
549
|
-
* container, so the next key reaches it; give the container a `tabindex`. A double-click on a marked field that is not editable yet asks for it
|
|
550
|
-
* to be (`EditAsked`).
|
|
551
|
-
*
|
|
552
|
-
* It reads `innerText`, never `innerHTML`, so what arrives is text. A field is
|
|
553
|
-
* one line unless it carries `aria-multiline="true"`: in one line, a line
|
|
554
|
-
* break becomes a space. Nothing is reported while an input method composes;
|
|
555
|
-
* the composed text arrives once, when it is done.
|
|
556
|
-
*
|
|
557
|
-
* Enter commits (Shift+Enter breaks a line in a multiline field), and so does
|
|
558
|
-
* leaving the field. Escape cancels, and puts the text the field had when
|
|
559
|
-
* editing began back in the DOM, since a view that stopped redrawing the field
|
|
560
|
-
* while it was edited will not. Where the browser lacks `plaintext-only`, a
|
|
561
|
-
* paste is inserted as text. What an edit means is the parent's `update`.
|
|
562
|
-
*/
|
|
563
543
|
const TextEdited = Schema.TaggedStruct("TextEdited", {
|
|
564
544
|
/** The field, by its marking attribute's value. */
|
|
565
545
|
field: Schema.String,
|
|
@@ -599,6 +579,28 @@ const caretAtEnd = (field) => {
|
|
|
599
579
|
};
|
|
600
580
|
/** Whether a key press belongs to an input method still composing, not to the field. */
|
|
601
581
|
const composing = (event) => event.isComposing || event.keyCode === 229;
|
|
582
|
+
/**
|
|
583
|
+
* Text typed into an editable descendant of an element, as a Mount: one set of
|
|
584
|
+
* listeners on the container, like `Targets`. A field is a descendant marked
|
|
585
|
+
* by an attribute whose value names it, while it is `contenteditable`. The
|
|
586
|
+
* view decides which one is; the Mount focuses it as it becomes so (made
|
|
587
|
+
* editable, or drawn editable), with the caret at its end, and at no other
|
|
588
|
+
* time. When Enter or Escape ends an edit and the view then removes the field
|
|
589
|
+
* or makes it no longer editable, focus, left on nothing, comes back to the
|
|
590
|
+
* container, so the next key reaches it; give the container a `tabindex`. A double-click on a marked field that is not editable yet asks for it
|
|
591
|
+
* to be (`EditAsked`).
|
|
592
|
+
*
|
|
593
|
+
* It reads `innerText`, never `innerHTML`, so what arrives is text. A field is
|
|
594
|
+
* one line unless it carries `aria-multiline="true"`: in one line, a line
|
|
595
|
+
* break becomes a space. Nothing is reported while an input method composes;
|
|
596
|
+
* the composed text arrives once, when it is done.
|
|
597
|
+
*
|
|
598
|
+
* Enter commits (Shift+Enter breaks a line in a multiline field), and so does
|
|
599
|
+
* leaving the field. Escape cancels, and puts the text the field had when
|
|
600
|
+
* editing began back in the DOM, since a view that stopped redrawing the field
|
|
601
|
+
* while it was edited will not. Where the browser lacks `plaintext-only`, a
|
|
602
|
+
* paste is inserted as text. What an edit means is the parent's `update`.
|
|
603
|
+
*/
|
|
602
604
|
const EditableText = Mount.defineStream("EditableText", {
|
|
603
605
|
messages: [
|
|
604
606
|
TextEdited,
|
|
@@ -817,4 +819,4 @@ const FollowTabStop = Mount.defineStream("FollowTabStop", {
|
|
|
817
819
|
//#endregion
|
|
818
820
|
export { MoveEnded as C, ScrollLock as D, HideOutside as E, FocusScope as O, Move as S, Moved as T, zoneOf as _, TextCommitted as a, Targets as b, DRAG_THRESHOLD as c, DragPlace as d, DragStarted as f, boxOf as g, PointerDrag as h, TextCancelled as i, tabbableWithin as k, DragCancelled as l, DraggedOver as m, EditAsked as n, TextEdited as o, DragZone as p, EditableText as r, textOf as s, FollowTabStop as t, DragDropped as u, TargetHovered as v, MoveStarted as w, targetOf as x, TargetPressed as y };
|
|
819
821
|
|
|
820
|
-
//# sourceMappingURL=follow-tab-stop-
|
|
822
|
+
//# sourceMappingURL=follow-tab-stop-Ci1tqAIW.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"follow-tab-stop-Ci1tqAIW.mjs","names":[],"sources":["../src/dom/focus-scope.ts","../src/dom/layers.ts","../src/dom/move.ts","../src/dom/targets.ts","../src/dom/pointer-drag.ts","../src/dom/editable-text.ts","../src/dom/follow-tab-stop.ts"],"sourcesContent":["import { Effect, Schema, Stream } from 'effect'\nimport * as Mount from 'foldkit/mount'\n\nconst FOCUSABLE = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]',\n '[contenteditable=\"true\"]',\n].join(', ')\n\nconst isVisible = (element: Element): boolean => {\n const probe = element as { checkVisibility?: () => boolean }\n return element.closest('[hidden], [inert]') === null && (probe.checkVisibility?.() ?? true)\n}\n\n/** Tabbable descendants in DOM order: focusable, visible, and not `tabindex=\"-1\"`. */\nexport const tabbableWithin = (root: Element): ReadonlyArray<HTMLElement> =>\n Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n element => element.tabIndex >= 0 && isVisible(element),\n )\n\nconst focusFirst = (elements: ReadonlyArray<HTMLElement>): boolean => {\n const first = elements[0]\n if (first === undefined) return false\n first.focus()\n return true\n}\n\n/**\n * A focus scope as a Mount: on insert it remembers what was focused, focuses\n * an initial element inside, and with `contain` keeps Tab and Shift+Tab\n * cycling inside; on unmount it gives focus back. Nothing crosses to the\n * Model: which element had focus is a DOM fact, so this is element lifecycle,\n * not state. It emits no Messages.\n */\nexport const FocusScope = Mount.defineStream('FocusScope', {\n messages: [Schema.Never],\n args: {\n /** Keep Tab, Shift+Tab, and a stray focus inside. */\n contain: Schema.Boolean,\n /** Give focus back to what had it, on unmount. */\n restore: Schema.Boolean,\n /** A selector inside the element to focus first; else the first tabbable\n * descendant, else the element itself when it is focusable. */\n initialFocus: Schema.NullOr(Schema.String),\n },\n execute: ({ element, contain, restore, initialFocus }) =>\n Stream.callback<never>(() =>\n Effect.acquireRelease(\n Effect.sync(() => {\n const ownerDocument = element.ownerDocument\n const previous = ownerDocument.activeElement\n const initial =\n initialFocus === null ? null : element.querySelector<HTMLElement>(initialFocus)\n if (initial !== null) initial.focus()\n else if (!focusFirst(tabbableWithin(element)) && element instanceof HTMLElement) {\n element.focus()\n }\n const onKeyDown = (event: Event) => {\n const key = event as KeyboardEvent\n if (key.key !== 'Tab') return\n const tabbable = tabbableWithin(element)\n const first = tabbable[0]\n const last = tabbable[tabbable.length - 1]\n if (first === undefined || last === undefined) {\n key.preventDefault()\n return\n }\n const active = ownerDocument.activeElement\n if (key.shiftKey && (active === first || !element.contains(active))) {\n key.preventDefault()\n last.focus()\n } else if (!key.shiftKey && (active === last || !element.contains(active))) {\n key.preventDefault()\n first.focus()\n }\n }\n const onFocusIn = (event: Event) => {\n const target = event.target\n if (target instanceof Node && element.contains(target)) return\n focusFirst(tabbableWithin(element))\n }\n if (contain) {\n element.addEventListener('keydown', onKeyDown)\n ownerDocument.addEventListener('focusin', onFocusIn)\n }\n return { previous, onKeyDown, onFocusIn, ownerDocument }\n }),\n ({ previous, onKeyDown, onFocusIn, ownerDocument }) =>\n Effect.sync(() => {\n if (contain) {\n element.removeEventListener('keydown', onKeyDown)\n ownerDocument.removeEventListener('focusin', onFocusIn)\n }\n if (restore && previous instanceof HTMLElement && ownerDocument.contains(previous)) {\n previous.focus()\n }\n }),\n ),\n ),\n})\n","/**\n * Two Mounts over Foldkit's own DOM helpers, for an overlay that is not a\n * native `<dialog>` or `popover`:\n *\n * - `ScrollLock` locks the document's scroll while mounted. Foldkit's lock is\n * refcounted, so nested overlays release it only when the last one goes.\n * - `HideOutside` marks everything outside the element inert while mounted,\n * through `Dom.inertOthers`, keyed by an id this Mount mints.\n *\n * Both emit no Messages; they are element lifecycle.\n */\nimport { Effect, Schema, Stream } from 'effect'\nimport * as Dom from 'foldkit/dom'\nimport * as Mount from 'foldkit/mount'\n\n/** Locks the document's scroll while the element is mounted; nested locks release with the last. */\nexport const ScrollLock = Mount.defineStream('ScrollLock', {\n messages: [Schema.Never],\n execute: () =>\n Stream.callback<never>(() => Effect.acquireRelease(Dom.lockScroll, () => Dom.unlockScroll)),\n})\n\nconst HIDE_OUTSIDE_ATTRIBUTE = 'data-foldkit-plus-hide-outside'\n\nlet minted = 0\n\n/** Makes everything outside the element inert while it is mounted, and restores it after. */\nexport const HideOutside = Mount.defineStream('HideOutside', {\n messages: [Schema.Never],\n execute: ({ element }) =>\n Stream.callback<never>(() =>\n Effect.acquireRelease(\n Effect.gen(function* () {\n // A per-mount id: Foldkit keys the inert set by it, so two overlays\n // restore independently. The attribute is how the element is found.\n const id = `hide-outside-${(minted += 1)}`\n element.setAttribute(HIDE_OUTSIDE_ATTRIBUTE, id)\n yield* Dom.inertOthers(id, [`[${HIDE_OUTSIDE_ATTRIBUTE}=\"${id}\"]`])\n return id\n }),\n id =>\n Effect.andThen(Dom.restoreInert(id), () =>\n Effect.sync(() => element.removeAttribute(HIDE_OUTSIDE_ATTRIBUTE)),\n ),\n ),\n ),\n})\n","import { Effect, Queue, Schema, Stream } from 'effect'\nimport * as Mount from 'foldkit/mount'\n\nexport const MoveStarted = Schema.TaggedStruct('MoveStarted', {\n pointerId: Schema.Number,\n pointerType: Schema.String,\n})\nexport type MoveStarted = typeof MoveStarted.Type\n\nexport const Moved = Schema.TaggedStruct('Moved', {\n pointerId: Schema.Number,\n /** From where the pointer went down, in CSS pixels. */\n deltaX: Schema.Number,\n deltaY: Schema.Number,\n pointerType: Schema.String,\n})\nexport type Moved = typeof Moved.Type\n\nexport const MoveEnded = Schema.TaggedStruct('MoveEnded', {\n pointerId: Schema.Number,\n /** `false` when the pointer was cancelled or capture was lost. */\n completed: Schema.Boolean,\n})\nexport type MoveEnded = typeof MoveEnded.Type\n\nexport type Fact = MoveStarted | Moved | MoveEnded\n\ntype PointerLike = Event & {\n readonly pointerId?: number\n readonly button?: number\n readonly pointerType?: string\n readonly clientX?: number\n readonly clientY?: number\n}\n\ntype Capturing = Element & {\n setPointerCapture?: (pointerId: number) => void\n releasePointerCapture?: (pointerId: number) => void\n}\n\n/**\n * Pointer movement on an element as a Mount: a primary-button pointer down\n * captures the pointer, each move reports the delta from where it started,\n * and up, cancel, or lost capture ends it. Nothing is decided here: a drag\n * threshold, a snap, or a constraint is the parent's `update`. Capture is\n * released with the Mount.\n */\nexport const Move = Mount.defineStream('Move', {\n messages: [MoveStarted, Moved, MoveEnded],\n execute: ({ element }) =>\n Stream.callback<Fact>(queue =>\n Effect.acquireRelease(\n Effect.sync(() => {\n const target = element as Capturing\n let active: {\n readonly pointerId: number\n readonly x: number\n readonly y: number\n } | null = null\n const offer = (fact: Fact) => Queue.offerUnsafe(queue, fact)\n const end = (pointerId: number, completed: boolean) => {\n if (active === null || active.pointerId !== pointerId) return\n active = null\n try {\n target.releasePointerCapture?.(pointerId)\n } catch {\n // Capture may already be gone; the end is still reported.\n }\n offer(MoveEnded.make({ pointerId, completed }))\n }\n const listeners: ReadonlyArray<readonly [string, (event: Event) => void]> = [\n [\n 'pointerdown',\n event => {\n const pointer = event as PointerLike\n if ((pointer.button ?? 0) !== 0 || active !== null) return\n const pointerId = pointer.pointerId ?? 0\n active = { pointerId, x: pointer.clientX ?? 0, y: pointer.clientY ?? 0 }\n try {\n target.setPointerCapture?.(pointerId)\n } catch {\n // Without capture the move still reports while over the element.\n }\n offer(MoveStarted.make({ pointerId, pointerType: pointer.pointerType ?? 'mouse' }))\n },\n ],\n [\n 'pointermove',\n event => {\n const pointer = event as PointerLike\n const pointerId = pointer.pointerId ?? 0\n if (active === null || active.pointerId !== pointerId) return\n offer(\n Moved.make({\n pointerId,\n deltaX: (pointer.clientX ?? 0) - active.x,\n deltaY: (pointer.clientY ?? 0) - active.y,\n pointerType: pointer.pointerType ?? 'mouse',\n }),\n )\n },\n ],\n ['pointerup', event => end((event as PointerLike).pointerId ?? 0, true)],\n ['pointercancel', event => end((event as PointerLike).pointerId ?? 0, false)],\n ['lostpointercapture', event => end((event as PointerLike).pointerId ?? 0, false)],\n ]\n for (const [type, listener] of listeners) element.addEventListener(type, listener)\n return { listeners, current: () => active }\n }),\n ({ listeners, current }) =>\n Effect.sync(() => {\n for (const [type, listener] of listeners) element.removeEventListener(type, listener)\n const pointer = current()\n if (pointer !== null) {\n try {\n ;(element as Capturing).releasePointerCapture?.(pointer.pointerId)\n } catch {\n // Already released.\n }\n }\n }),\n ),\n ),\n})\n","import { Effect, Queue, Schema, Stream } from 'effect'\nimport * as Mount from 'foldkit/mount'\n\nexport const TargetHovered = Schema.TaggedStruct('TargetHovered', {\n /** The marked descendant under the pointer, or `null` when there is none. */\n id: Schema.NullOr(Schema.String),\n})\nexport type TargetHovered = typeof TargetHovered.Type\n\nexport const TargetPressed = Schema.TaggedStruct('TargetPressed', {\n id: Schema.String,\n shiftKey: Schema.Boolean,\n altKey: Schema.Boolean,\n ctrlKey: Schema.Boolean,\n metaKey: Schema.Boolean,\n})\nexport type TargetPressed = typeof TargetPressed.Type\n\nexport type TargetFact = TargetHovered | TargetPressed\n\ntype Modified = Event & {\n readonly shiftKey?: boolean\n readonly altKey?: boolean\n readonly ctrlKey?: boolean\n readonly metaKey?: boolean\n}\n\n/** The id of the nearest marked element at or above `from`, inside `container`. */\nexport const targetOf = (container: Element, from: EventTarget | null, attribute: string) => {\n if (!(from instanceof Element)) return null\n const marked = from.closest(`[${attribute}]`)\n return marked !== null && container.contains(marked) ? marked.getAttribute(attribute) : null\n}\n\n/**\n * Which marked descendant of an element the pointer is over, and which one was\n * pressed, as a Mount: one set of listeners on the container, not one per item.\n * A descendant is marked by an attribute whose value is its id, such as a\n * canvas's `data-composition-node` or a table's `data-row`. The nearest marked\n * ancestor of the event's target, inside the container, is the one reported.\n *\n * What hovering or pressing means (a selection, an outline, a menu) is the\n * parent's `update`.\n */\nexport const Targets = Mount.defineStream('Targets', {\n messages: [TargetHovered, TargetPressed],\n args: {\n /** The attribute that marks a descendant, holding its id. */\n attribute: Schema.String,\n /** Prevent a press's default, such as a link navigating inside an editor's canvas. */\n preventDefault: Schema.Boolean,\n },\n execute: ({ element, attribute, preventDefault }) =>\n Stream.callback<TargetFact>(queue =>\n Effect.acquireRelease(\n Effect.sync(() => {\n let hovered: string | null = null\n const hover = (id: string | null) => {\n if (id === hovered) return\n hovered = id\n Queue.offerUnsafe(queue, TargetHovered.make({ id }))\n }\n const listeners: ReadonlyArray<readonly [string, (event: Event) => void]> = [\n ['pointerover', event => hover(targetOf(element, event.target, attribute))],\n ['pointerleave', () => hover(null)],\n [\n 'click',\n event => {\n const id = targetOf(element, event.target, attribute)\n if (id === null) return\n if (preventDefault) event.preventDefault()\n const modified = event as Modified\n Queue.offerUnsafe(\n queue,\n TargetPressed.make({\n id,\n shiftKey: modified.shiftKey === true,\n altKey: modified.altKey === true,\n ctrlKey: modified.ctrlKey === true,\n metaKey: modified.metaKey === true,\n }),\n )\n },\n ],\n ]\n for (const [type, listener] of listeners) element.addEventListener(type, listener)\n return listeners\n }),\n listeners =>\n Effect.sync(() => {\n for (const [type, listener] of listeners) element.removeEventListener(type, listener)\n }),\n ),\n ),\n})\n","import { Effect, Queue, Schema, Stream } from 'effect'\nimport * as Mount from 'foldkit/mount'\nimport { targetOf } from './targets.js'\n\nexport const DragZone = Schema.Literals(['before', 'inside', 'after'])\nexport type DragZone = typeof DragZone.Type\n\n/**\n * A marked descendant under the pointer, and the zone of it the pointer is in.\n * `part` is the value of the `part` attribute on the element found, such as\n * the empty Region of a node a drop goes into; a place with one is `inside`.\n */\nexport const DragPlace = Schema.Struct({\n id: Schema.String,\n zone: DragZone,\n part: Schema.optionalKey(Schema.String),\n})\nexport type DragPlace = typeof DragPlace.Type\n\nexport const DragStarted = Schema.TaggedStruct('DragStarted', {\n /** The marked descendant being dragged. */\n id: Schema.String,\n})\nexport type DragStarted = typeof DragStarted.Type\n\nexport const DraggedOver = Schema.TaggedStruct('DraggedOver', {\n /** Where the pointer is, or `null` over nothing marked, or over what is dragged. */\n over: Schema.NullOr(DragPlace),\n /**\n * For a drag onto `targets`: whether the pointer is inside the region they\n * are in, so `over: null` with `region: true` is its empty space (an empty\n * page, the space below the last node). Absent for a drag among its own.\n */\n region: Schema.optionalKey(Schema.Boolean),\n})\nexport type DraggedOver = typeof DraggedOver.Type\n\nexport const DragDropped = Schema.TaggedStruct('DragDropped', {\n id: Schema.String,\n over: Schema.NullOr(DragPlace),\n /** As `DraggedOver`'s: released inside the `targets` region. */\n region: Schema.optionalKey(Schema.Boolean),\n})\nexport type DragDropped = typeof DragDropped.Type\n\nexport const DragCancelled = Schema.TaggedStruct('DragCancelled', { id: Schema.String })\nexport type DragCancelled = typeof DragCancelled.Type\n\nexport type DragFact = DragStarted | DraggedOver | DragDropped | DragCancelled\n\n/** How far, in CSS pixels, a press moves before it is a drag rather than a click. */\nexport const DRAG_THRESHOLD = 4\n\ninterface Box {\n readonly top: number\n readonly height: number\n}\n\n/** The zone of a box a vertical position falls in, by thirds. */\nexport const zoneOf = (box: Box, y: number): DragZone => {\n if (box.height <= 0) return 'inside'\n const at = (y - box.top) / box.height\n return at < 1 / 3 ? 'before' : at > 2 / 3 ? 'after' : 'inside'\n}\n\n/**\n * An element's box. An element drawn as `display: contents` has none, so its\n * first child's stands for it, as an editor's node wrapper's does.\n */\nexport const boxOf = (element: Element): Box => {\n const own = element.getBoundingClientRect()\n if (own.width > 0 || own.height > 0) return own\n const child = element.firstElementChild\n return child === null ? own : boxOf(child)\n}\n\ntype Positioned = Event & {\n readonly clientX?: number\n readonly clientY?: number\n readonly button?: number\n readonly buttons?: number\n readonly pointerId?: number\n readonly key?: string\n}\n\n/**\n * Dragging one marked descendant of an element onto another, as a Mount. A\n * descendant is marked by an attribute whose value is its id, as `Targets`\n * marks them; a press on one that then moves past a threshold starts a drag,\n * and the pointer's place over another marked descendant is reported with the\n * zone of it the pointer is in: its first third (`before`), its middle\n * (`inside`), or its last third (`after`). Releasing drops, and Escape or a\n * cancelled pointer ends the drag with nothing dropped.\n *\n * It writes no roles, no `tabindex` and no key handling, so the marked\n * elements can be anything: a tree's rows, a canvas's nodes. The keyboard's way\n * to do what a drag does is the parent's to give. Geometry is measured when the\n * pointer moves and never kept; what a drop means is the parent's `update`.\n */\nexport const PointerDrag = Mount.defineStream('PointerDrag', {\n messages: [DragStarted, DraggedOver, DragDropped, DragCancelled],\n args: {\n /** The attribute that marks a descendant, holding its id. */\n attribute: Schema.String,\n /**\n * Where a drag may land, when not on the element's own marked descendants:\n * the elements marked by `attribute` inside the one `within` selects, such\n * as a palette's Blocks dragged onto a page. A drop's `over` is then one of\n * those; what is dragged is still one of the element's own. `within` is\n * looked for nearest first, under the element's closest ancestor that holds\n * a match, so two editors on one page each drop onto their own.\n */\n targets: Schema.optionalKey(Schema.Struct({ attribute: Schema.String, within: Schema.String })),\n /**\n * An attribute that names a part of what a drop lands on, on an element\n * also marked as one: a place over it carries the part and is `inside`.\n */\n part: Schema.optionalKey(Schema.String),\n },\n execute: ({ element, attribute, targets, part }) =>\n Stream.callback<DragFact>(queue =>\n Effect.acquireRelease(\n Effect.sync(() => {\n const owner = element.ownerDocument\n // What a drop lands on: the element's own marked descendants, or those of `targets`.\n const landsOn = targets?.attribute ?? attribute\n // Looked for at each move, so a redraw that replaced it is followed.\n const regionOf = (within: string): Element | null => {\n for (let at = element.parentElement; at !== null; at = at.parentElement) {\n const found = at.querySelector(within)\n if (found !== null) return found\n }\n return null\n }\n const find = (from: EventTarget | null) => {\n if (!(from instanceof Element)) return { marked: null, inRegion: false }\n const region = targets === undefined ? element : regionOf(targets.within)\n const marked = from.closest(`[${landsOn}]`)\n return {\n marked: marked !== null && region !== null && region.contains(marked) ? marked : null,\n // Only a drag onto `targets` tells its region's empty space from elsewhere.\n inRegion: targets !== undefined && region !== null && region.contains(from),\n }\n }\n // A press not yet a drag, or a drag under way: one pointer's, the first down.\n let pressed: {\n readonly id: string\n readonly pointer: number | undefined\n readonly x: number\n readonly y: number\n } | null = null\n let dragging: string | null = null\n let over: DragPlace | null = null\n let inRegion = false\n // The click that ends a drag is not a press on what it ends over.\n let swallowClick = false\n\n const placeAt = (event: Positioned) => {\n // Touch and pen capture the pointer to where it went down, so the\n // event's target is the dragged element: ask what is under it instead.\n const under =\n typeof owner.elementFromPoint === 'function' && event.clientX !== undefined\n ? owner.elementFromPoint(event.clientX, event.clientY ?? 0)\n : null\n const { marked, inRegion: region } = find(under ?? event.target)\n const id = marked?.getAttribute(landsOn) ?? null\n // Over itself is over nothing, where what is dragged is among what it lands on.\n const named = part === undefined ? null : (marked?.getAttribute(part) ?? null)\n const place: DragPlace | null =\n marked === null || id === null || (targets === undefined && id === dragging)\n ? null\n : named === null\n ? { id, zone: zoneOf(boxOf(marked), event.clientY ?? 0) }\n : { id, zone: 'inside', part: named }\n return { place, region }\n }\n /** The region flag a fact carries: only for a drag onto `targets`. */\n const withRegion = (region: boolean) => (targets === undefined ? {} : { region })\n const ours = (event: Positioned) =>\n pressed !== null &&\n (pressed.pointer === undefined || event.pointerId === pressed.pointer)\n // `inside`: the release was on the container, so its click will reach it.\n // The release is hit-tested again: the layout may have moved since\n // the last move (a scroll, a row that shifted), and the drop says\n // where the pointer was let go, not where it was last seen. A\n // release with no position of its own keeps the last move's answer.\n const end = (dropped: boolean, at?: Positioned, inside = false) => {\n const id = dragging\n if (id !== null && at?.clientX !== undefined && at.clientY !== undefined) {\n const { place, region } = placeAt(at)\n over = place\n inRegion = region\n }\n pressed = null\n dragging = null\n const last = over\n const lastRegion = inRegion\n over = null\n inRegion = false\n release()\n if (id === null) return\n swallowClick = dropped && inside\n Queue.offerUnsafe(\n queue,\n dropped\n ? DragDropped.make({ id, over: last, ...withRegion(lastRegion) })\n : DragCancelled.make({ id }),\n )\n }\n\n const onDocument: ReadonlyArray<readonly [string, (event: Event) => void]> = [\n [\n 'pointermove',\n event => {\n const positioned = event as Positioned\n if (!ours(positioned) || pressed === null) return\n // Released where no pointerup reached us, such as over a frame.\n if (positioned.buttons !== undefined && (positioned.buttons & 1) === 0)\n return end(false)\n if (dragging === null) {\n const moved = Math.hypot(\n (positioned.clientX ?? 0) - pressed.x,\n (positioned.clientY ?? 0) - pressed.y,\n )\n if (moved < DRAG_THRESHOLD) return\n dragging = pressed.id\n owner.getSelection()?.removeAllRanges()\n Queue.offerUnsafe(queue, DragStarted.make({ id: dragging }))\n }\n const { place, region } = placeAt(positioned)\n if (\n place?.id === over?.id &&\n place?.zone === over?.zone &&\n place?.part === over?.part &&\n region === inRegion\n )\n return\n over = place\n inRegion = region\n Queue.offerUnsafe(queue, DraggedOver.make({ over: place, ...withRegion(region) }))\n },\n ],\n [\n 'pointerup',\n event => {\n if (!ours(event as Positioned)) return\n if (dragging === null) end(false)\n else\n end(\n true,\n event as Positioned,\n event.target instanceof Node && element.contains(event.target),\n )\n },\n ],\n [\n 'pointercancel',\n event => {\n if (ours(event as Positioned)) end(false)\n },\n ],\n ]\n // Heard on the way down, and kept there: an Escape that ends a drag is the\n // drag's, not also the focused element's (a canvas that would deselect).\n const onEscape = (event: Event) => {\n if (pressed === null || (event as Positioned).key !== 'Escape') return\n event.preventDefault()\n event.stopPropagation()\n end(false)\n }\n // The document is listened to only while a press is under way.\n let listening = false\n const listen = () => {\n if (listening) return\n listening = true\n for (const [type, listener] of onDocument) owner.addEventListener(type, listener)\n owner.addEventListener('keydown', onEscape, true)\n }\n const release = () => {\n if (!listening) return\n listening = false\n for (const [type, listener] of onDocument) owner.removeEventListener(type, listener)\n owner.removeEventListener('keydown', onEscape, true)\n }\n\n const onElement: ReadonlyArray<readonly [string, (event: Event) => void, boolean]> = [\n [\n 'pointerdown',\n event => {\n const positioned = event as Positioned\n // Any press is past the drop a swallowed click was waiting for.\n swallowClick = false\n // A second pointer while one is down is not a new drag.\n if (pressed !== null) return\n if ((positioned.button ?? 0) !== 0) return\n // A press in text being edited selects text: it is no drag.\n if (\n event.target instanceof Element &&\n event.target.closest('[contenteditable]:not([contenteditable=\"false\"])') !== null\n )\n return\n const id = targetOf(element, event.target, attribute)\n if (id === null) return\n pressed = {\n id,\n pointer: positioned.pointerId,\n x: positioned.clientX ?? 0,\n y: positioned.clientY ?? 0,\n }\n listen()\n },\n false,\n ],\n [\n 'click',\n event => {\n if (!swallowClick) return\n swallowClick = false\n event.preventDefault()\n event.stopImmediatePropagation()\n },\n true,\n ],\n // A link or an image would start the browser's own drag instead.\n ['dragstart', event => event.preventDefault(), false],\n ]\n for (const [type, listener, capture] of onElement)\n element.addEventListener(type, listener, capture)\n return { onElement, release }\n }),\n ({ onElement, release }) =>\n Effect.sync(() => {\n for (const [type, listener, capture] of onElement)\n element.removeEventListener(type, listener, capture)\n release()\n }),\n ),\n ),\n})\n","import { Effect, Queue, Schema, Stream } from 'effect'\nimport * as Mount from 'foldkit/mount'\n\nexport const TextEdited = Schema.TaggedStruct('TextEdited', {\n /** The field, by its marking attribute's value. */\n field: Schema.String,\n text: Schema.String,\n})\nexport type TextEdited = typeof TextEdited.Type\n\nexport const TextCommitted = Schema.TaggedStruct('TextCommitted', {\n field: Schema.String,\n text: Schema.String,\n})\nexport type TextCommitted = typeof TextCommitted.Type\n\nexport const TextCancelled = Schema.TaggedStruct('TextCancelled', {\n field: Schema.String,\n /** The text the field had when editing began, which the DOM shows again. */\n initial: Schema.String,\n})\nexport type TextCancelled = typeof TextCancelled.Type\n\nexport const EditAsked = Schema.TaggedStruct('EditAsked', {\n /** A marked field, not editable yet, that was double-clicked. */\n field: Schema.String,\n})\nexport type EditAsked = typeof EditAsked.Type\n\nexport type TextFact = TextEdited | TextCommitted | TextCancelled | EditAsked\n\n/** Whether a field takes more than one line. */\nconst multiline = (element: HTMLElement) => element.getAttribute('aria-multiline') === 'true'\n\n/**\n * A field's text. A browser ends an edited element with a line break it adds\n * itself, which is not the author's; in one line, the author's breaks are spaces.\n */\nexport const textOf = (element: HTMLElement): string => {\n const text = element.innerText.replace(/\\r\\n?/g, '\\n').replace(/\\n$/, '')\n return multiline(element) ? text : text.replace(/\\n+/g, ' ')\n}\n\n/** Puts the caret at the end of a field, which focuses it too. */\nconst caretAtEnd = (field: HTMLElement) => {\n // Chromium also focuses on the selection below; no standard says a browser must.\n field.focus()\n const caret = document.createRange()\n caret.selectNodeContents(field)\n caret.collapse(false)\n const selection = window.getSelection()\n selection?.removeAllRanges()\n selection?.addRange(caret)\n}\n\n/** Whether a key press belongs to an input method still composing, not to the field. */\nconst composing = (event: KeyboardEvent) => event.isComposing || event.keyCode === 229\n\n/**\n * Text typed into an editable descendant of an element, as a Mount: one set of\n * listeners on the container, like `Targets`. A field is a descendant marked\n * by an attribute whose value names it, while it is `contenteditable`. The\n * view decides which one is; the Mount focuses it as it becomes so (made\n * editable, or drawn editable), with the caret at its end, and at no other\n * time. When Enter or Escape ends an edit and the view then removes the field\n * or makes it no longer editable, focus, left on nothing, comes back to the\n * container, so the next key reaches it; give the container a `tabindex`. A double-click on a marked field that is not editable yet asks for it\n * to be (`EditAsked`).\n *\n * It reads `innerText`, never `innerHTML`, so what arrives is text. A field is\n * one line unless it carries `aria-multiline=\"true\"`: in one line, a line\n * break becomes a space. Nothing is reported while an input method composes;\n * the composed text arrives once, when it is done.\n *\n * Enter commits (Shift+Enter breaks a line in a multiline field), and so does\n * leaving the field. Escape cancels, and puts the text the field had when\n * editing began back in the DOM, since a view that stopped redrawing the field\n * while it was edited will not. Where the browser lacks `plaintext-only`, a\n * paste is inserted as text. What an edit means is the parent's `update`.\n */\nexport const EditableText = Mount.defineStream('EditableText', {\n messages: [TextEdited, TextCommitted, TextCancelled, EditAsked],\n args: {\n /** The attribute that marks a field, holding its name. */\n attribute: Schema.String,\n },\n execute: ({ element, attribute }) =>\n Stream.callback<TextFact>(queue =>\n Effect.acquireRelease(\n Effect.sync(() => {\n /** The field being edited: its name, its text when editing began, and the last reported. */\n let current:\n | {\n readonly field: HTMLElement\n readonly name: string\n readonly initial: string\n reported: string\n ended: boolean\n }\n | undefined\n let composingText = false\n // A field whose edit a key ended: when it leaves with focus on nothing, focus\n // comes back to the container, where the keys that began the edit are.\n let ending: HTMLElement | undefined\n\n /** The marked field at or above an event's target, inside the container. */\n const markedOf = (target: EventTarget | null): HTMLElement | undefined => {\n if (!(target instanceof Element)) return undefined\n const field = target.closest(`[${attribute}]`)\n return field instanceof HTMLElement && element.contains(field) ? field : undefined\n }\n /** The same, while it is editable. */\n const fieldOf = (target: EventTarget | null): HTMLElement | undefined => {\n const field = markedOf(target)\n return field?.isContentEditable === true ? field : undefined\n }\n /** Begins an edit of `field`, unless one of it is under way: on focus, or typing. */\n const begin = (field: HTMLElement) => {\n if (current?.field === field && !current.ended) return current\n const text = textOf(field)\n current = {\n field,\n name: field.getAttribute(attribute) ?? '',\n initial: text,\n reported: text,\n ended: false,\n }\n return current\n }\n /**\n * The edit of `field`: the one under way or just ended, else one begun\n * now, for a field focused before these listeners came. An ended edit\n * reports nothing more, so the blur that follows a commit or a cancel\n * does not commit again.\n */\n const editOf = (field: HTMLElement) => (current?.field === field ? current : begin(field))\n const report = (field: HTMLElement) => {\n const edit = editOf(field)\n if (edit.ended) return\n const text = textOf(field)\n // A browser may follow a composition with an input of the same text.\n if (text === edit.reported) return\n edit.reported = text\n Queue.offerUnsafe(queue, TextEdited.make({ field: edit.name, text }))\n }\n const commit = (field: HTMLElement) => {\n const edit = editOf(field)\n if (edit.ended) return\n edit.ended = true\n Queue.offerUnsafe(queue, TextCommitted.make({ field: edit.name, text: textOf(field) }))\n }\n const cancel = (field: HTMLElement) => {\n const edit = editOf(field)\n if (edit.ended) return\n edit.ended = true\n field.innerText = edit.initial\n // Writing the text moved the caret to its start; typing on goes after it.\n caretAtEnd(field)\n Queue.offerUnsafe(\n queue,\n TextCancelled.make({ field: edit.name, initial: edit.initial }),\n )\n }\n\n const listeners: ReadonlyArray<readonly [string, (event: Event) => void]> = [\n [\n 'dblclick',\n event => {\n const field = markedOf(event.target)\n // In an editable field, a double-click selects a word.\n if (field === undefined || field.isContentEditable) return\n Queue.offerUnsafe(\n queue,\n EditAsked.make({ field: field.getAttribute(attribute) ?? '' }),\n )\n },\n ],\n [\n 'focusin',\n event => {\n // A composition that never ended, in a field left, holds no other field silent.\n composingText = false\n const field = fieldOf(event.target)\n if (field !== undefined) begin(field)\n },\n ],\n // Before the change: a field focused before these listeners came is begun here.\n [\n 'beforeinput',\n event => {\n const field = fieldOf(event.target)\n if (field !== undefined) begin(field)\n },\n ],\n ['compositionstart', () => (composingText = true)],\n [\n 'compositionend',\n event => {\n composingText = false\n const field = fieldOf(event.target)\n if (field !== undefined) report(field)\n },\n ],\n [\n 'input',\n event => {\n const field = fieldOf(event.target)\n if (field !== undefined && !composingText) report(field)\n },\n ],\n [\n 'keydown',\n event => {\n const field = fieldOf(event.target)\n const key = event as KeyboardEvent\n if (field === undefined || composing(key)) return\n if (key.key === 'Escape') {\n event.preventDefault()\n ending = field\n cancel(field)\n } else if (key.key === 'Enter' && !(key.shiftKey && multiline(field))) {\n event.preventDefault()\n ending = field\n commit(field)\n }\n },\n ],\n [\n 'focusout',\n event => {\n const field = fieldOf(event.target)\n if (field === undefined) return\n // Left while still there and editable, asked once the change that may have\n // taken it away is done: the author went elsewhere, and focus is theirs.\n queueMicrotask(() => {\n if (field === ending && field.isConnected && field.isContentEditable)\n ending = undefined\n })\n commit(field)\n },\n ],\n [\n 'paste',\n event => {\n const field = fieldOf(event.target)\n // `plaintext-only` pastes text itself; elsewhere, markup would arrive.\n if (field === undefined || field.contentEditable === 'plaintext-only') return\n const pasted = (event as ClipboardEvent).clipboardData?.getData('text/plain') ?? ''\n event.preventDefault()\n // `insertText` fires no `beforeinput`: begun here, a paste after Enter is an edit.\n begin(field)\n // As typing is, so the browser's undo takes it back; `textOf` makes one line of it.\n document.execCommand('insertText', false, pasted)\n },\n ],\n ]\n // Focuses a field as it becomes editable, and only then: made so, or drawn so.\n // A field editable all along, or another going away, moves no focus.\n const editable = `[${attribute}][contenteditable]:not([contenteditable=\"false\"])`\n const becoming = new MutationObserver(records => {\n if (ending !== undefined && !(ending.isConnected && ending.isContentEditable)) {\n ending = undefined\n const focused = document.activeElement\n if ((focused === null || focused === document.body) && element instanceof HTMLElement)\n element.focus({ preventScroll: true })\n }\n for (const record of records) {\n const changed =\n record.type === 'attributes' ? [record.target] : Array.from(record.addedNodes)\n for (const node of changed) {\n if (!(node instanceof HTMLElement)) continue\n const field = node.matches(editable) ? node : node.querySelector(editable)\n if (field instanceof HTMLElement) return caretAtEnd(field)\n }\n }\n })\n\n for (const [type, listener] of listeners) element.addEventListener(type, listener)\n // A field made editable, drawn editable, or leaving.\n becoming.observe(element, {\n subtree: true,\n childList: true,\n attributes: true,\n attributeFilter: ['contenteditable'],\n })\n return { listeners, becoming }\n }),\n ({ listeners, becoming }) =>\n Effect.sync(() => {\n becoming.disconnect()\n for (const [type, listener] of listeners) element.removeEventListener(type, listener)\n }),\n ),\n ),\n})\n","import { Effect, Schema, Stream } from 'effect'\nimport * as Mount from 'foldkit/mount'\n\n/**\n * Keeps focus that is inside a container of roving tab stops on its stop, as a\n * Mount: when a transition moves the stop (a row made current by a command the\n * container did not see, such as a duplicate) or removes the focused\n * descendant (a deleted row), focus goes to the descendant with\n * `tabindex=\"0\"`. It sends no Message. Focus outside the container is never\n * taken, and focus the user moved away stays where it went.\n */\nexport const FollowTabStop = Mount.defineStream('FollowTabStop', {\n messages: [Schema.Never],\n execute: ({ element }) =>\n Stream.callback<never>(() =>\n Effect.acquireRelease(\n Effect.sync(() => {\n let within = element.contains(document.activeElement)\n const entered = () => {\n within = true\n }\n const left = (event: Event) => {\n const next = (event as FocusEvent).relatedTarget\n if (next instanceof Node && element.contains(next)) return\n const from = event.target\n // Asked once the change that may have taken it away is done: a descendant\n // removed with focus in it keeps the claim, one the user left does not.\n queueMicrotask(() => {\n if (!(from instanceof Node) || from.isConnected) within = false\n })\n }\n const follow = () => {\n if (!within) return\n const stop = element.querySelector('[tabindex=\"0\"]')\n const focused = document.activeElement\n if (!(stop instanceof HTMLElement) || stop === focused) return\n const lost = focused === null || focused === document.body\n const passed =\n focused !== null &&\n element.contains(focused) &&\n focused.getAttribute('tabindex') === '-1'\n if (lost || passed) stop.focus()\n }\n const changes = new MutationObserver(follow)\n element.addEventListener('focusin', entered)\n element.addEventListener('focusout', left)\n // A stop moved, or a descendant drawn or removed.\n changes.observe(element, {\n subtree: true,\n childList: true,\n attributes: true,\n attributeFilter: ['tabindex'],\n })\n return () => {\n changes.disconnect()\n element.removeEventListener('focusin', entered)\n element.removeEventListener('focusout', left)\n }\n }),\n stop => Effect.sync(stop),\n ),\n ),\n})\n"],"mappings":"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"}
|