foldkit-primitives 0.9.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -1
- package/dist/device/index.d.mts +69 -36
- 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 +68 -19
- package/dist/dom/index.d.mts.map +1 -1
- package/dist/dom/index.mjs +32 -25
- 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-CmmxqSEi.d.mts} +34 -15
- package/dist/index-CmmxqSEi.d.mts.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +3 -3
- 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 +63 -25
- 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
|
@@ -3,6 +3,13 @@ import * as Mount from "foldkit/mount";
|
|
|
3
3
|
//#region src/dom/focus-scope.d.ts
|
|
4
4
|
/** Tabbable descendants in DOM order: focusable, visible, and not `tabindex="-1"`. */
|
|
5
5
|
declare const tabbableWithin: (root: Element) => ReadonlyArray<HTMLElement>;
|
|
6
|
+
/**
|
|
7
|
+
* A focus scope as a Mount: on insert it remembers what was focused, focuses
|
|
8
|
+
* an initial element inside, and with `contain` keeps Tab and Shift+Tab
|
|
9
|
+
* cycling inside; on unmount it gives focus back. Nothing crosses to the
|
|
10
|
+
* Model: which element had focus is a DOM fact, so this is element lifecycle,
|
|
11
|
+
* not state. It emits no Messages.
|
|
12
|
+
*/
|
|
6
13
|
declare const FocusScope: Mount.MountDefinitionWithArgs<"FocusScope", {
|
|
7
14
|
/** Keep Tab, Shift+Tab, and a stray focus inside. */
|
|
8
15
|
contain: Schema.Boolean;
|
|
@@ -14,7 +21,9 @@ declare const FocusScope: Mount.MountDefinitionWithArgs<"FocusScope", {
|
|
|
14
21
|
}, never>;
|
|
15
22
|
//#endregion
|
|
16
23
|
//#region src/dom/layers.d.ts
|
|
24
|
+
/** Locks the document's scroll while the element is mounted; nested locks release with the last. */
|
|
17
25
|
declare const ScrollLock: Mount.MountDefinitionNoArgs<"ScrollLock", never>;
|
|
26
|
+
/** Makes everything outside the element inert while it is mounted, and restores it after. */
|
|
18
27
|
declare const HideOutside: Mount.MountDefinitionNoArgs<"HideOutside", never>;
|
|
19
28
|
//#endregion
|
|
20
29
|
//#region src/dom/move.d.ts
|
|
@@ -38,6 +47,13 @@ declare const MoveEnded: Schema.TaggedStruct<"MoveEnded", {
|
|
|
38
47
|
}>;
|
|
39
48
|
type MoveEnded = typeof MoveEnded.Type;
|
|
40
49
|
type Fact = MoveStarted | Moved | MoveEnded;
|
|
50
|
+
/**
|
|
51
|
+
* Pointer movement on an element as a Mount: a primary-button pointer down
|
|
52
|
+
* captures the pointer, each move reports the delta from where it started,
|
|
53
|
+
* and up, cancel, or lost capture ends it. Nothing is decided here: a drag
|
|
54
|
+
* threshold, a snap, or a constraint is the parent's `update`. Capture is
|
|
55
|
+
* released with the Mount.
|
|
56
|
+
*/
|
|
41
57
|
declare const Move: Mount.MountDefinitionNoArgs<"Move", {
|
|
42
58
|
readonly _tag: "MoveStarted";
|
|
43
59
|
readonly pointerId: number;
|
|
@@ -71,6 +87,16 @@ type TargetPressed = typeof TargetPressed.Type;
|
|
|
71
87
|
type TargetFact = TargetHovered | TargetPressed;
|
|
72
88
|
/** The id of the nearest marked element at or above `from`, inside `container`. */
|
|
73
89
|
declare const targetOf: (container: Element, from: EventTarget | null, attribute: string) => string | null;
|
|
90
|
+
/**
|
|
91
|
+
* Which marked descendant of an element the pointer is over, and which one was
|
|
92
|
+
* pressed, as a Mount: one set of listeners on the container, not one per item.
|
|
93
|
+
* A descendant is marked by an attribute whose value is its id, such as a
|
|
94
|
+
* canvas's `data-composition-node` or a table's `data-row`. The nearest marked
|
|
95
|
+
* ancestor of the event's target, inside the container, is the one reported.
|
|
96
|
+
*
|
|
97
|
+
* What hovering or pressing means (a selection, an outline, a menu) is the
|
|
98
|
+
* parent's `update`.
|
|
99
|
+
*/
|
|
74
100
|
declare const Targets: Mount.MountDefinitionWithArgs<"Targets", {
|
|
75
101
|
/** The attribute that marks a descendant, holding its id. */
|
|
76
102
|
attribute: Schema.String;
|
|
@@ -151,6 +177,20 @@ declare const zoneOf: (box: Box, y: number) => DragZone;
|
|
|
151
177
|
* first child's stands for it, as an editor's node wrapper's does.
|
|
152
178
|
*/
|
|
153
179
|
declare const boxOf: (element: Element) => Box;
|
|
180
|
+
/**
|
|
181
|
+
* Dragging one marked descendant of an element onto another, as a Mount. A
|
|
182
|
+
* descendant is marked by an attribute whose value is its id, as `Targets`
|
|
183
|
+
* marks them; a press on one that then moves past a threshold starts a drag,
|
|
184
|
+
* and the pointer's place over another marked descendant is reported with the
|
|
185
|
+
* zone of it the pointer is in: its first third (`before`), its middle
|
|
186
|
+
* (`inside`), or its last third (`after`). Releasing drops, and Escape or a
|
|
187
|
+
* cancelled pointer ends the drag with nothing dropped.
|
|
188
|
+
*
|
|
189
|
+
* It writes no roles, no `tabindex` and no key handling, so the marked
|
|
190
|
+
* elements can be anything: a tree's rows, a canvas's nodes. The keyboard's way
|
|
191
|
+
* to do what a drag does is the parent's to give. Geometry is measured when the
|
|
192
|
+
* pointer moves and never kept; what a drop means is the parent's `update`.
|
|
193
|
+
*/
|
|
154
194
|
declare const PointerDrag: Mount.MountDefinitionWithArgs<"PointerDrag", {
|
|
155
195
|
/** The attribute that marks a descendant, holding its id. */
|
|
156
196
|
attribute: Schema.String;
|
|
@@ -178,7 +218,7 @@ declare const PointerDrag: Mount.MountDefinitionWithArgs<"PointerDrag", {
|
|
|
178
218
|
readonly _tag: "DraggedOver";
|
|
179
219
|
readonly over: {
|
|
180
220
|
readonly id: string;
|
|
181
|
-
readonly zone: "
|
|
221
|
+
readonly zone: "after" | "before" | "inside";
|
|
182
222
|
readonly part?: string;
|
|
183
223
|
} | null;
|
|
184
224
|
readonly region?: boolean;
|
|
@@ -187,7 +227,7 @@ declare const PointerDrag: Mount.MountDefinitionWithArgs<"PointerDrag", {
|
|
|
187
227
|
readonly id: string;
|
|
188
228
|
readonly over: {
|
|
189
229
|
readonly id: string;
|
|
190
|
-
readonly zone: "
|
|
230
|
+
readonly zone: "after" | "before" | "inside";
|
|
191
231
|
readonly part?: string;
|
|
192
232
|
} | null;
|
|
193
233
|
readonly region?: boolean;
|
|
@@ -225,6 +265,28 @@ type TextFact = TextEdited | TextCommitted | TextCancelled | EditAsked;
|
|
|
225
265
|
* itself, which is not the author's; in one line, the author's breaks are spaces.
|
|
226
266
|
*/
|
|
227
267
|
declare const textOf: (element: HTMLElement) => string;
|
|
268
|
+
/**
|
|
269
|
+
* Text typed into an editable descendant of an element, as a Mount: one set of
|
|
270
|
+
* listeners on the container, like `Targets`. A field is a descendant marked
|
|
271
|
+
* by an attribute whose value names it, while it is `contenteditable`. The
|
|
272
|
+
* view decides which one is; the Mount focuses it as it becomes so (made
|
|
273
|
+
* editable, or drawn editable), with the caret at its end, and at no other
|
|
274
|
+
* time. When Enter or Escape ends an edit and the view then removes the field
|
|
275
|
+
* or makes it no longer editable, focus, left on nothing, comes back to the
|
|
276
|
+
* 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
|
|
277
|
+
* to be (`EditAsked`).
|
|
278
|
+
*
|
|
279
|
+
* It reads `innerText`, never `innerHTML`, so what arrives is text. A field is
|
|
280
|
+
* one line unless it carries `aria-multiline="true"`: in one line, a line
|
|
281
|
+
* break becomes a space. Nothing is reported while an input method composes;
|
|
282
|
+
* the composed text arrives once, when it is done.
|
|
283
|
+
*
|
|
284
|
+
* Enter commits (Shift+Enter breaks a line in a multiline field), and so does
|
|
285
|
+
* leaving the field. Escape cancels, and puts the text the field had when
|
|
286
|
+
* editing began back in the DOM, since a view that stopped redrawing the field
|
|
287
|
+
* while it was edited will not. Where the browser lacks `plaintext-only`, a
|
|
288
|
+
* paste is inserted as text. What an edit means is the parent's `update`.
|
|
289
|
+
*/
|
|
228
290
|
declare const EditableText: Mount.MountDefinitionWithArgs<"EditableText", {
|
|
229
291
|
/** The attribute that marks a field, holding its name. */
|
|
230
292
|
attribute: Schema.String;
|
|
@@ -246,4 +308,4 @@ declare const EditableText: Mount.MountDefinitionWithArgs<"EditableText", {
|
|
|
246
308
|
}>;
|
|
247
309
|
//#endregion
|
|
248
310
|
export { ScrollLock as A, targetOf as C, MoveStarted as D, MoveEnded as E, tabbableWithin as M, Moved as O, Targets as S, Move as T, boxOf as _, TextEdited as a, TargetHovered as b, DRAG_THRESHOLD as c, DragFact as d, DragPlace as f, PointerDrag as g, DraggedOver as h, TextCommitted as i, FocusScope as j, HideOutside as k, DragCancelled as l, DragZone as m, EditableText as n, TextFact as o, DragStarted as p, TextCancelled as r, textOf as s, EditAsked as t, DragDropped as u, zoneOf as v, Fact as w, TargetPressed as x, TargetFact as y };
|
|
249
|
-
//# sourceMappingURL=editable-text-
|
|
311
|
+
//# sourceMappingURL=editable-text-Dvm-Fuqs.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editable-text-Dvm-Fuqs.d.mts","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"],"mappings":";;;;cAmBa,iBAAc,MAAU,YAAU,cAAc;;;;;;;;cAmBhD,YAAU,MAAA;;;;;;;;;;;;cCtBV,YAAU,MAAA;;cAWV,aAAW,MAAA;;;cCxBX,aAAW,OAAA;;;;KAIZ,qBAAqB,YAAY;cAEhC,OAAK,OAAA;;;;;;;KAON,eAAe,MAAM;cAEpB,WAAS,OAAA;;;;;KAKV,mBAAmB,UAAU;KAE7B,OAAO,cAAc,QAAQ;;;;;;;;cAsB5B,MAAI,MAAA;;;;;;;;;;;;;;;;;cC5CJ,eAAa,OAAA;;;;KAId,uBAAuB,cAAc;cAEpC,eAAa,OAAA;;;;;;;KAOd,uBAAuB,cAAc;KAErC,aAAa,gBAAgB;;cAU5B,WAAQ,WAAe,SAAO,MAAQ,oBAAkB;;;;;;;;;;;cAgBxD,SAAO,MAAA;;;;;;;;;;;;;;;;;;cCxCP,UAAQ,OAAA;KACT,kBAAkB,SAAS;;;;;;cAO1B,WAAS,OAAA;;;;;KAKV,mBAAmB,UAAU;cAE5B,aAAW,OAAA;;eAAX,OAAA;;KAID,qBAAqB,YAAY;cAEhC,aAAW,OAAA;;;;;;;;;;;;;;KAUZ,qBAAqB,YAAY;cAEhC,aAAW,OAAA;;;;;;;;;;KAMZ,qBAAqB,YAAY;cAEhC,eAAa,OAAA;eAAb,OAAA;;KACD,uBAAuB,cAAc;KAErC,WAAW,cAAc,cAAc,cAAc;;cAGpD;UAEH;WACC;WACA;;;cAIE,SAAM,KAAS,KAAG,cAAc;;;;;cAUhC,QAAK,SAAa,YAAU;;;;;;;;;;;;;;;cA8B5B,aAAW,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cChGX,YAAU,OAAA;;;;;KAKX,oBAAoB,WAAW;cAE9B,eAAa,OAAA;;;;KAId,uBAAuB,cAAc;cAEpC,eAAa,OAAA;;;;;KAKd,uBAAuB,cAAc;cAEpC,WAAS,OAAA;;kBAAT,OAAA;;KAID,mBAAmB,UAAU;KAE7B,WAAW,aAAa,gBAAgB,gBAAgB;;;;;cASvD,SAAM,SAAa;;;;;;;;;;;;;;;;;;;;;;;cA0CnB,cAAY,MAAA;;aAAZ,OAAA"}
|
package/dist/events/index.d.mts
CHANGED
|
@@ -1,22 +1,30 @@
|
|
|
1
|
-
import { a as modalityOfKey, i as Modality, n as InputModalityMessage, r as InputModalityModel, t as InputModality } from "../input-modality-
|
|
1
|
+
import { a as modalityOfKey, i as Modality, n as InputModalityMessage, r as InputModalityModel, t as InputModality } from "../input-modality-BY-bSrEc.mjs";
|
|
2
2
|
import { Schema, Stream } from "effect";
|
|
3
3
|
import { Bundle } from "foldkit-bundle";
|
|
4
4
|
//#region src/events/active-element.d.ts
|
|
5
5
|
export declare const ActiveElementMessage: import("foldkit/message").MessageUnion<{
|
|
6
6
|
readonly Changed: {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
tag: Schema.String;
|
|
8
|
+
id: Schema.NullOr<Schema.String>;
|
|
9
9
|
};
|
|
10
10
|
}>;
|
|
11
11
|
export type ActiveElementMessage = typeof ActiveElementMessage.Type;
|
|
12
12
|
/**
|
|
13
13
|
* Focus changes on the document. Without a document the stream is empty
|
|
14
|
-
* instead of throwing. Lift with `Subscription.
|
|
14
|
+
* instead of throwing. Lift with `Subscription.persistentEntry`, mapping into
|
|
15
15
|
* the parent's Message.
|
|
16
16
|
*/
|
|
17
17
|
export declare const activeElementEvents: () => Stream.Stream<ActiveElementMessage>;
|
|
18
18
|
//#endregion
|
|
19
19
|
//#region src/events/hotkeys.d.ts
|
|
20
|
+
export interface KeyPress {
|
|
21
|
+
readonly key: string;
|
|
22
|
+
readonly repeat: boolean;
|
|
23
|
+
readonly ctrl: boolean;
|
|
24
|
+
readonly shift: boolean;
|
|
25
|
+
readonly alt: boolean;
|
|
26
|
+
readonly meta: boolean;
|
|
27
|
+
}
|
|
20
28
|
/**
|
|
21
29
|
* Hotkey matching as a pure function over a `Pressed` payload: `matchHotkey`
|
|
22
30
|
* answers whether a press is the shortcut, so `update` stays a table of
|
|
@@ -29,14 +37,6 @@ export declare const activeElementEvents: () => Stream.Stream<ActiveElementMessa
|
|
|
29
37
|
* no key, or two keys, matches nothing: predicates return false, they do
|
|
30
38
|
* not throw on the hot path.
|
|
31
39
|
*/
|
|
32
|
-
export interface KeyPress {
|
|
33
|
-
readonly key: string;
|
|
34
|
-
readonly repeat: boolean;
|
|
35
|
-
readonly ctrl: boolean;
|
|
36
|
-
readonly shift: boolean;
|
|
37
|
-
readonly alt: boolean;
|
|
38
|
-
readonly meta: boolean;
|
|
39
|
-
}
|
|
40
40
|
export declare const matchHotkey: (pattern: string, press: KeyPress) => boolean;
|
|
41
41
|
//#endregion
|
|
42
42
|
//#region src/events/idle.d.ts
|
|
@@ -49,34 +49,42 @@ export declare const IdleMessage: import("foldkit/message").MessageUnion<{
|
|
|
49
49
|
readonly BecameActive: {};
|
|
50
50
|
}>;
|
|
51
51
|
export type IdleMessage = typeof IdleMessage.Type;
|
|
52
|
+
/**
|
|
53
|
+
* Idleness as a bundle: `idle` in the Model, flipped by activity and
|
|
54
|
+
* silence. While active, activity debounced past `timeoutMs` settles to
|
|
55
|
+
* `BecameIdle`; while idle, the first activity wakes to `BecameActive` and
|
|
56
|
+
* the dependency flip restarts the watch. Starts active — a lurker idles
|
|
57
|
+
* when the timeout elapses. The clock is Effect's, so tests drive it with
|
|
58
|
+
* TestClock.
|
|
59
|
+
*/
|
|
52
60
|
export declare const Idle: Bundle.Bundle<"Idle", {
|
|
53
61
|
readonly timeoutMs: number;
|
|
54
62
|
}, {
|
|
55
63
|
readonly idle: boolean;
|
|
56
64
|
}, {
|
|
57
|
-
readonly _tag: "BecameIdle";
|
|
58
|
-
} | {
|
|
59
65
|
readonly _tag: "BecameActive";
|
|
66
|
+
} | {
|
|
67
|
+
readonly _tag: "BecameIdle";
|
|
60
68
|
}, never, never, never, void, {}, {}>;
|
|
61
69
|
//#endregion
|
|
62
70
|
//#region src/events/keyboard.d.ts
|
|
63
71
|
export declare const KeyboardMessage: import("foldkit/message").MessageUnion<{
|
|
64
72
|
readonly Pressed: {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
key: Schema.String;
|
|
74
|
+
repeat: Schema.Boolean;
|
|
75
|
+
ctrl: Schema.Boolean;
|
|
76
|
+
shift: Schema.Boolean;
|
|
77
|
+
alt: Schema.Boolean;
|
|
78
|
+
meta: Schema.Boolean;
|
|
71
79
|
};
|
|
72
80
|
readonly Released: {
|
|
73
|
-
|
|
81
|
+
key: Schema.String;
|
|
74
82
|
};
|
|
75
83
|
}>;
|
|
76
84
|
export type KeyboardMessage = typeof KeyboardMessage.Type;
|
|
77
85
|
/**
|
|
78
86
|
* Key presses and releases on the window. Without a window the stream is
|
|
79
|
-
* empty instead of throwing. Lift with `Subscription.
|
|
87
|
+
* empty instead of throwing. Lift with `Subscription.persistentEntry`, mapping
|
|
80
88
|
* into the parent's Message.
|
|
81
89
|
*/
|
|
82
90
|
export declare const keyboardEvents: (options?: {
|
|
@@ -91,14 +99,14 @@ export declare const keyboardEvents: (options?: {
|
|
|
91
99
|
//#region src/events/pointer.d.ts
|
|
92
100
|
export declare const PointerMessage: import("foldkit/message").MessageUnion<{
|
|
93
101
|
readonly Moved: {
|
|
94
|
-
|
|
95
|
-
|
|
102
|
+
x: Schema.Number;
|
|
103
|
+
y: Schema.Number;
|
|
96
104
|
};
|
|
97
105
|
}>;
|
|
98
106
|
export type PointerMessage = typeof PointerMessage.Type;
|
|
99
107
|
/**
|
|
100
108
|
* Pointer moves on the window. Without a window the stream is empty instead
|
|
101
|
-
* of throwing. Lift with `Subscription.
|
|
109
|
+
* of throwing. Lift with `Subscription.persistentEntry`, mapping into the
|
|
102
110
|
* parent's Message.
|
|
103
111
|
*/
|
|
104
112
|
export declare const pointerEvents: () => Stream.Stream<PointerMessage>;
|
|
@@ -106,15 +114,15 @@ export declare const pointerEvents: () => Stream.Stream<PointerMessage>;
|
|
|
106
114
|
//#region src/events/scroll.d.ts
|
|
107
115
|
export declare const ScrollMessage: import("foldkit/message").MessageUnion<{
|
|
108
116
|
readonly Scrolled: {
|
|
109
|
-
|
|
110
|
-
|
|
117
|
+
x: Schema.Number;
|
|
118
|
+
y: Schema.Number;
|
|
111
119
|
};
|
|
112
120
|
}>;
|
|
113
121
|
export type ScrollMessage = typeof ScrollMessage.Type;
|
|
114
122
|
/**
|
|
115
123
|
* Scrolls on the window, in the capture phase so container scrolls (which
|
|
116
124
|
* do not bubble) report too. Without a window the stream is empty instead
|
|
117
|
-
* of throwing. Lift with `Subscription.
|
|
125
|
+
* of throwing. Lift with `Subscription.persistentEntry`, mapping into the
|
|
118
126
|
* parent's Message.
|
|
119
127
|
*/
|
|
120
128
|
export declare const scrollEvents: () => Stream.Stream<ScrollMessage>;
|
|
@@ -126,10 +134,16 @@ export declare const VisibilityModel: Schema.Struct<{
|
|
|
126
134
|
export type VisibilityModel = typeof VisibilityModel.Type;
|
|
127
135
|
export declare const VisibilityMessage: import("foldkit/message").MessageUnion<{
|
|
128
136
|
readonly Changed: {
|
|
129
|
-
|
|
137
|
+
visible: Schema.Boolean;
|
|
130
138
|
};
|
|
131
139
|
}>;
|
|
132
140
|
export type VisibilityMessage = typeof VisibilityMessage.Type;
|
|
141
|
+
/**
|
|
142
|
+
* Page visibility as a bundle: `visible` in the Model, read from the
|
|
143
|
+
* document at startup and kept current by `visibilitychange`. A hidden page
|
|
144
|
+
* is the application's cue to pause polling, animations, and realtime
|
|
145
|
+
* streams; the decision stays in application `update`, not here.
|
|
146
|
+
*/
|
|
133
147
|
export declare const Visibility: Bundle.Bundle<"Visibility", void, {
|
|
134
148
|
readonly visible: boolean;
|
|
135
149
|
}, {
|
|
@@ -145,11 +159,18 @@ export declare const WindowSizeModel: Schema.Struct<{
|
|
|
145
159
|
export type WindowSizeModel = typeof WindowSizeModel.Type;
|
|
146
160
|
export declare const WindowSizeMessage: import("foldkit/message").MessageUnion<{
|
|
147
161
|
readonly Changed: {
|
|
148
|
-
|
|
149
|
-
|
|
162
|
+
width: Schema.Number;
|
|
163
|
+
height: Schema.Number;
|
|
150
164
|
};
|
|
151
165
|
}>;
|
|
152
166
|
export type WindowSizeMessage = typeof WindowSizeMessage.Type;
|
|
167
|
+
/**
|
|
168
|
+
* Window size as a bundle: `{ width, height }` in the Model, read from the
|
|
169
|
+
* window at startup and kept current by one `resize` listener. Breakpoints
|
|
170
|
+
* owns its own listener and derives names; place this one when views need
|
|
171
|
+
* the raw pixels. Placing both doubles resize listeners — pick the one the
|
|
172
|
+
* view reads.
|
|
173
|
+
*/
|
|
153
174
|
export declare const WindowSize: Bundle.Bundle<"WindowSize", void, {
|
|
154
175
|
readonly width: number;
|
|
155
176
|
readonly height: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","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"],"mappings":";;;;qBASa,gDAAoB
|
|
1
|
+
{"version":3,"file":"index.d.mts","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"],"mappings":";;;;qBASa,gDAAoB;;IACpB,KAAG,OAAA;IAAiB,IAAE,OAAA,OAAA,OAAA;;;YAEvB,8BAA8B,qBAAqB;;;;;;qBAOlD,2BAA0B,OAAO,OAAO;;;iBCnBpC;WACN;WACA;WACA;WACA;WACA;WACA;;;;;;;;;;;;;;qBA8CE,cAAW,iBAAmB,OAAS;;;qBC9CvC,WAAS,OAAA;iBAAT,OAAA;;YACD,mBAAmB,UAAU;qBAE5B,uCAAW;;;;YACZ,qBAAqB,YAAY;;;;;;;;;qBAsBhC,MAAI,OAAA;;;;;;;;;;;qBCtBJ,2CAAe;;IAExB,KAAG,OAAA;IACH,QAAM,OAAA;IACN,MAAI,OAAA;IACJ,OAAK,OAAA;IACL,KAAG,OAAA;IACH,MAAI,OAAA;;;IAEM,KAAG,OAAA;;;YAEL,yBAAyB,gBAAgB;;;;;;qBAOxC,iBAAc;;;;;;WAOd,kBAAkB,OAAO;MAEnC,OAAO,OAAO;;;qBC5BJ,0CAAc;;IAChB,GAAC,OAAA;IAAiB,GAAC,OAAA;;;YAElB,wBAAwB,eAAe;;;;;;qBAOtC,qBAAoB,OAAO,OAAO;;;qBCVlC,yCAAa;;IACZ,GAAC,OAAA;IAAiB,GAAC,OAAA;;;YAErB,uBAAuB,cAAc;;;;;;;qBAQpC,oBAAmB,OAAO,OAAO;;;qBCfjC,iBAAe,OAAA;oBAAf,OAAA;;YACD,yBAAyB,gBAAgB;qBAExC,6CAAiB;;IAAmC,SAAO,OAAA;;;YAC5D,2BAA2B,kBAAkB;;;;;;;qBAuB5C,YAAU,OAAA;;;;;;;;qBC1BV,iBAAe,OAAA;;;;YAChB,yBAAyB,gBAAgB;qBAExC,6CAAiB;;IACjB,OAAK,OAAA;IAAiB,QAAM,OAAA;;;YAE7B,2BAA2B,kBAAkB;;;;;;;;qBA0C5C,YAAU,OAAA"}
|
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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"}
|