foldkit-primitives 0.2.1 → 0.4.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 +514 -43
- package/dist/device/index.d.mts +11 -11
- package/dist/device/index.d.mts.map +1 -1
- package/dist/device/index.mjs +95 -87
- package/dist/device/index.mjs.map +1 -1
- package/dist/dom/index.d.mts +71 -1
- package/dist/dom/index.d.mts.map +1 -1
- package/dist/dom/index.mjs +303 -5
- package/dist/dom/index.mjs.map +1 -1
- package/dist/editable-text-CKkS_mWt.d.mts +235 -0
- package/dist/editable-text-CKkS_mWt.d.mts.map +1 -0
- package/dist/events/index.d.mts +10 -1
- package/dist/events/index.d.mts.map +1 -1
- package/dist/events/index.mjs +34 -18
- package/dist/events/index.mjs.map +1 -1
- package/dist/follow-tab-stop-DpxwLXvU.mjs +800 -0
- package/dist/follow-tab-stop-DpxwLXvU.mjs.map +1 -0
- package/dist/index-Cppf-lBx.d.mts +130 -0
- package/dist/index-Cppf-lBx.d.mts.map +1 -0
- package/dist/index.d.mts +8 -6
- package/dist/index.mjs +8 -6
- package/dist/input-modality-DGOhAknO.d.mts +26 -0
- package/dist/input-modality-DGOhAknO.d.mts.map +1 -0
- package/dist/input-modality-m6v7__Xs.mjs +43 -0
- package/dist/input-modality-m6v7__Xs.mjs.map +1 -0
- package/dist/interaction/index.d.mts +1017 -0
- package/dist/interaction/index.d.mts.map +1 -0
- package/dist/interaction/index.mjs +1647 -0
- package/dist/interaction/index.mjs.map +1 -0
- package/dist/internal-DP-xLKbT.mjs +37 -0
- package/dist/internal-DP-xLKbT.mjs.map +1 -0
- package/dist/media/index.d.mts.map +1 -1
- package/dist/media/index.mjs +3 -3
- package/dist/media/index.mjs.map +1 -1
- package/dist/motion/index.d.mts +2 -112
- package/dist/motion/index.mjs +2 -208
- package/dist/motion-DGGBZ0wt.mjs +240 -0
- package/dist/motion-DGGBZ0wt.mjs.map +1 -0
- package/dist/net/index.d.mts +68 -4
- package/dist/net/index.d.mts.map +1 -1
- package/dist/net/index.mjs +139 -56
- package/dist/net/index.mjs.map +1 -1
- package/dist/observers/index.d.mts +1 -1
- package/dist/observers/index.mjs +3 -0
- package/dist/observers/index.mjs.map +1 -1
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/state/index.d.mts +57 -15
- package/dist/state/index.d.mts.map +1 -1
- package/dist/state/index.mjs +198 -90
- package/dist/state/index.mjs.map +1 -1
- package/dist/time/index.d.mts +21 -1
- package/dist/time/index.d.mts.map +1 -1
- package/dist/time/index.mjs +48 -19
- package/dist/time/index.mjs.map +1 -1
- package/package.json +31 -8
- package/dist/motion/index.d.mts.map +0 -1
- package/dist/motion/index.mjs.map +0 -1
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import * as Mount from "foldkit/mount";
|
|
3
|
+
//#region src/dom/focus-scope.d.ts
|
|
4
|
+
/** Tabbable descendants in DOM order: focusable, visible, and not `tabindex="-1"`. */
|
|
5
|
+
declare const tabbableWithin: (root: Element) => ReadonlyArray<HTMLElement>;
|
|
6
|
+
declare const FocusScope: Mount.MountDefinitionWithArgs<"FocusScope", {
|
|
7
|
+
/** Keep Tab, Shift+Tab, and a stray focus inside. */
|
|
8
|
+
contain: Schema.Boolean;
|
|
9
|
+
/** Give focus back to what had it, on unmount. */
|
|
10
|
+
restore: Schema.Boolean;
|
|
11
|
+
/** A selector inside the element to focus first; else the first tabbable
|
|
12
|
+
* descendant, else the element itself when it is focusable. */
|
|
13
|
+
initialFocus: Schema.NullOr<Schema.String>;
|
|
14
|
+
}, never>;
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/dom/layers.d.ts
|
|
17
|
+
declare const ScrollLock: Mount.MountDefinitionNoArgs<"ScrollLock", never>;
|
|
18
|
+
declare const HideOutside: Mount.MountDefinitionNoArgs<"HideOutside", never>;
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/dom/move.d.ts
|
|
21
|
+
declare const MoveStarted: Schema.TaggedStruct<"MoveStarted", {
|
|
22
|
+
readonly pointerId: Schema.Number;
|
|
23
|
+
readonly pointerType: Schema.String;
|
|
24
|
+
}>;
|
|
25
|
+
type MoveStarted = typeof MoveStarted.Type;
|
|
26
|
+
declare const Moved: Schema.TaggedStruct<"Moved", {
|
|
27
|
+
readonly pointerId: Schema.Number;
|
|
28
|
+
/** From where the pointer went down, in CSS pixels. */
|
|
29
|
+
readonly deltaX: Schema.Number;
|
|
30
|
+
readonly deltaY: Schema.Number;
|
|
31
|
+
readonly pointerType: Schema.String;
|
|
32
|
+
}>;
|
|
33
|
+
type Moved = typeof Moved.Type;
|
|
34
|
+
declare const MoveEnded: Schema.TaggedStruct<"MoveEnded", {
|
|
35
|
+
readonly pointerId: Schema.Number;
|
|
36
|
+
/** `false` when the pointer was cancelled or capture was lost. */
|
|
37
|
+
readonly completed: Schema.Boolean;
|
|
38
|
+
}>;
|
|
39
|
+
type MoveEnded = typeof MoveEnded.Type;
|
|
40
|
+
type Fact = MoveStarted | Moved | MoveEnded;
|
|
41
|
+
declare const Move: Mount.MountDefinitionNoArgs<"Move", {
|
|
42
|
+
readonly _tag: "MoveStarted";
|
|
43
|
+
readonly pointerId: number;
|
|
44
|
+
readonly pointerType: string;
|
|
45
|
+
} | {
|
|
46
|
+
readonly _tag: "Moved";
|
|
47
|
+
readonly pointerId: number;
|
|
48
|
+
readonly deltaX: number;
|
|
49
|
+
readonly deltaY: number;
|
|
50
|
+
readonly pointerType: string;
|
|
51
|
+
} | {
|
|
52
|
+
readonly _tag: "MoveEnded";
|
|
53
|
+
readonly pointerId: number;
|
|
54
|
+
readonly completed: boolean;
|
|
55
|
+
}>;
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/dom/targets.d.ts
|
|
58
|
+
declare const TargetHovered: Schema.TaggedStruct<"TargetHovered", {
|
|
59
|
+
/** The marked descendant under the pointer, or `null` when there is none. */
|
|
60
|
+
readonly id: Schema.NullOr<Schema.String>;
|
|
61
|
+
}>;
|
|
62
|
+
type TargetHovered = typeof TargetHovered.Type;
|
|
63
|
+
declare const TargetPressed: Schema.TaggedStruct<"TargetPressed", {
|
|
64
|
+
readonly id: Schema.String;
|
|
65
|
+
readonly shiftKey: Schema.Boolean;
|
|
66
|
+
readonly altKey: Schema.Boolean;
|
|
67
|
+
readonly ctrlKey: Schema.Boolean;
|
|
68
|
+
readonly metaKey: Schema.Boolean;
|
|
69
|
+
}>;
|
|
70
|
+
type TargetPressed = typeof TargetPressed.Type;
|
|
71
|
+
type TargetFact = TargetHovered | TargetPressed;
|
|
72
|
+
/** The id of the nearest marked element at or above `from`, inside `container`. */
|
|
73
|
+
declare const targetOf: (container: Element, from: EventTarget | null, attribute: string) => string | null;
|
|
74
|
+
declare const Targets: Mount.MountDefinitionWithArgs<"Targets", {
|
|
75
|
+
/** The attribute that marks a descendant, holding its id. */
|
|
76
|
+
attribute: Schema.String;
|
|
77
|
+
/** Prevent a press's default, such as a link navigating inside an editor's canvas. */
|
|
78
|
+
preventDefault: Schema.Boolean;
|
|
79
|
+
}, {
|
|
80
|
+
readonly _tag: "TargetHovered";
|
|
81
|
+
readonly id: string | null;
|
|
82
|
+
} | {
|
|
83
|
+
readonly _tag: "TargetPressed";
|
|
84
|
+
readonly id: string;
|
|
85
|
+
readonly shiftKey: boolean;
|
|
86
|
+
readonly altKey: boolean;
|
|
87
|
+
readonly ctrlKey: boolean;
|
|
88
|
+
readonly metaKey: boolean;
|
|
89
|
+
}>;
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/dom/pointer-drag.d.ts
|
|
92
|
+
declare const DragZone: Schema.Literals<readonly ["before", "inside", "after"]>;
|
|
93
|
+
type DragZone = typeof DragZone.Type;
|
|
94
|
+
/** A marked descendant under the pointer, and the zone of it the pointer is in. */
|
|
95
|
+
declare const DragPlace: Schema.Struct<{
|
|
96
|
+
readonly id: Schema.String;
|
|
97
|
+
readonly zone: Schema.Literals<readonly ["before", "inside", "after"]>;
|
|
98
|
+
}>;
|
|
99
|
+
type DragPlace = typeof DragPlace.Type;
|
|
100
|
+
declare const DragStarted: Schema.TaggedStruct<"DragStarted", {
|
|
101
|
+
/** The marked descendant being dragged. */
|
|
102
|
+
readonly id: Schema.String;
|
|
103
|
+
}>;
|
|
104
|
+
type DragStarted = typeof DragStarted.Type;
|
|
105
|
+
declare const DraggedOver: Schema.TaggedStruct<"DraggedOver", {
|
|
106
|
+
/** Where the pointer is, or `null` over nothing marked, or over what is dragged. */
|
|
107
|
+
readonly over: Schema.NullOr<Schema.Struct<{
|
|
108
|
+
readonly id: Schema.String;
|
|
109
|
+
readonly zone: Schema.Literals<readonly ["before", "inside", "after"]>;
|
|
110
|
+
}>>;
|
|
111
|
+
/**
|
|
112
|
+
* For a drag onto `targets`: whether the pointer is inside the region they
|
|
113
|
+
* are in, so `over: null` with `region: true` is its empty space (an empty
|
|
114
|
+
* page, the space below the last node). Absent for a drag among its own.
|
|
115
|
+
*/
|
|
116
|
+
readonly region: Schema.optionalKey<Schema.Boolean>;
|
|
117
|
+
}>;
|
|
118
|
+
type DraggedOver = typeof DraggedOver.Type;
|
|
119
|
+
declare const DragDropped: Schema.TaggedStruct<"DragDropped", {
|
|
120
|
+
readonly id: Schema.String;
|
|
121
|
+
readonly over: Schema.NullOr<Schema.Struct<{
|
|
122
|
+
readonly id: Schema.String;
|
|
123
|
+
readonly zone: Schema.Literals<readonly ["before", "inside", "after"]>;
|
|
124
|
+
}>>;
|
|
125
|
+
/** As `DraggedOver`'s: released inside the `targets` region. */
|
|
126
|
+
readonly region: Schema.optionalKey<Schema.Boolean>;
|
|
127
|
+
}>;
|
|
128
|
+
type DragDropped = typeof DragDropped.Type;
|
|
129
|
+
declare const DragCancelled: Schema.TaggedStruct<"DragCancelled", {
|
|
130
|
+
readonly id: Schema.String;
|
|
131
|
+
}>;
|
|
132
|
+
type DragCancelled = typeof DragCancelled.Type;
|
|
133
|
+
type DragFact = DragStarted | DraggedOver | DragDropped | DragCancelled;
|
|
134
|
+
/** How far, in CSS pixels, a press moves before it is a drag rather than a click. */
|
|
135
|
+
declare const DRAG_THRESHOLD = 4;
|
|
136
|
+
interface Box {
|
|
137
|
+
readonly top: number;
|
|
138
|
+
readonly height: number;
|
|
139
|
+
}
|
|
140
|
+
/** The zone of a box a vertical position falls in, by thirds. */
|
|
141
|
+
declare const zoneOf: (box: Box, y: number) => DragZone;
|
|
142
|
+
/**
|
|
143
|
+
* An element's box. An element drawn as `display: contents` has none, so its
|
|
144
|
+
* first child's stands for it, as an editor's node wrapper's does.
|
|
145
|
+
*/
|
|
146
|
+
declare const boxOf: (element: Element) => Box;
|
|
147
|
+
declare const PointerDrag: Mount.MountDefinitionWithArgs<"PointerDrag", {
|
|
148
|
+
/** The attribute that marks a descendant, holding its id. */
|
|
149
|
+
attribute: Schema.String;
|
|
150
|
+
/**
|
|
151
|
+
* Where a drag may land, when not on the element's own marked descendants:
|
|
152
|
+
* the elements marked by `attribute` inside the one `within` selects, such
|
|
153
|
+
* as a palette's Blocks dragged onto a page. A drop's `over` is then one of
|
|
154
|
+
* those; what is dragged is still one of the element's own. `within` is
|
|
155
|
+
* looked for nearest first, under the element's closest ancestor that holds
|
|
156
|
+
* a match, so two editors on one page each drop onto their own.
|
|
157
|
+
*/
|
|
158
|
+
targets: Schema.optionalKey<Schema.Struct<{
|
|
159
|
+
readonly attribute: Schema.String;
|
|
160
|
+
readonly within: Schema.String;
|
|
161
|
+
}>>;
|
|
162
|
+
}, {
|
|
163
|
+
readonly _tag: "DragStarted";
|
|
164
|
+
readonly id: string;
|
|
165
|
+
} | {
|
|
166
|
+
readonly _tag: "DraggedOver";
|
|
167
|
+
readonly over: {
|
|
168
|
+
readonly id: string;
|
|
169
|
+
readonly zone: "inside" | "before" | "after";
|
|
170
|
+
} | null;
|
|
171
|
+
readonly region?: boolean;
|
|
172
|
+
} | {
|
|
173
|
+
readonly _tag: "DragDropped";
|
|
174
|
+
readonly id: string;
|
|
175
|
+
readonly over: {
|
|
176
|
+
readonly id: string;
|
|
177
|
+
readonly zone: "inside" | "before" | "after";
|
|
178
|
+
} | null;
|
|
179
|
+
readonly region?: boolean;
|
|
180
|
+
} | {
|
|
181
|
+
readonly _tag: "DragCancelled";
|
|
182
|
+
readonly id: string;
|
|
183
|
+
}>;
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/dom/editable-text.d.ts
|
|
186
|
+
declare const TextEdited: Schema.TaggedStruct<"TextEdited", {
|
|
187
|
+
/** The field, by its marking attribute's value. */
|
|
188
|
+
readonly field: Schema.String;
|
|
189
|
+
readonly text: Schema.String;
|
|
190
|
+
}>;
|
|
191
|
+
type TextEdited = typeof TextEdited.Type;
|
|
192
|
+
declare const TextCommitted: Schema.TaggedStruct<"TextCommitted", {
|
|
193
|
+
readonly field: Schema.String;
|
|
194
|
+
readonly text: Schema.String;
|
|
195
|
+
}>;
|
|
196
|
+
type TextCommitted = typeof TextCommitted.Type;
|
|
197
|
+
declare const TextCancelled: Schema.TaggedStruct<"TextCancelled", {
|
|
198
|
+
readonly field: Schema.String;
|
|
199
|
+
/** The text the field had when editing began, which the DOM shows again. */
|
|
200
|
+
readonly initial: Schema.String;
|
|
201
|
+
}>;
|
|
202
|
+
type TextCancelled = typeof TextCancelled.Type;
|
|
203
|
+
declare const EditAsked: Schema.TaggedStruct<"EditAsked", {
|
|
204
|
+
/** A marked field, not editable yet, that was double-clicked. */
|
|
205
|
+
readonly field: Schema.String;
|
|
206
|
+
}>;
|
|
207
|
+
type EditAsked = typeof EditAsked.Type;
|
|
208
|
+
type TextFact = TextEdited | TextCommitted | TextCancelled | EditAsked;
|
|
209
|
+
/**
|
|
210
|
+
* A field's text. A browser ends an edited element with a line break it adds
|
|
211
|
+
* itself, which is not the author's; in one line, the author's breaks are spaces.
|
|
212
|
+
*/
|
|
213
|
+
declare const textOf: (element: HTMLElement) => string;
|
|
214
|
+
declare const EditableText: Mount.MountDefinitionWithArgs<"EditableText", {
|
|
215
|
+
/** The attribute that marks a field, holding its name. */
|
|
216
|
+
attribute: Schema.String;
|
|
217
|
+
}, {
|
|
218
|
+
readonly _tag: "TextEdited";
|
|
219
|
+
readonly field: string;
|
|
220
|
+
readonly text: string;
|
|
221
|
+
} | {
|
|
222
|
+
readonly _tag: "TextCommitted";
|
|
223
|
+
readonly field: string;
|
|
224
|
+
readonly text: string;
|
|
225
|
+
} | {
|
|
226
|
+
readonly _tag: "TextCancelled";
|
|
227
|
+
readonly field: string;
|
|
228
|
+
readonly initial: string;
|
|
229
|
+
} | {
|
|
230
|
+
readonly _tag: "EditAsked";
|
|
231
|
+
readonly field: string;
|
|
232
|
+
}>;
|
|
233
|
+
//#endregion
|
|
234
|
+
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 };
|
|
235
|
+
//# sourceMappingURL=editable-text-CKkS_mWt.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editable-text-CKkS_mWt.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":";;;;cA0Ba,iBAAkB,MAAM,YAAU,cAAc;cAYhD,YAAU,MAAA;;;;;;;;;;;cCvBV,YAAU,MAAA;cAUV,aAAW,MAAA;;;cCfX,aAAW,OAAA;;;;KAIZ,qBAAqB,YAAY;cAEhC,OAAK,OAAA;;;;;;;KAON,eAAe,MAAM;cAEpB,WAAS,OAAA;;;;;KAKV,mBAAmB,UAAU;KAE7B,OAAO,cAAc,QAAQ;cAe5B,MAAI,MAAA;;;;;;;;;;;;;;;;;cClCJ,eAAa,OAAA;;;;KAId,uBAAuB,cAAc;cAEpC,eAAa,OAAA;;;;;;;KAOd,uBAAuB,cAAc;KAErC,aAAa,gBAAgB;;cAU5B,WAAY,WAAW,SAAS,MAAM,oBAAoB;cAM1D,SAAO,MAAA;;;;;;;;;;;;;;;;;;cC1BP,UAAQ,OAAA;KACT,kBAAkB,SAAS;;cAG1B,WAAS,OAAA;;;;KACV,mBAAmB,UAAU;cAE5B,aAAW,OAAA;;;;KAIZ,qBAAqB,YAAY;cAEhC,aAAW,OAAA;;;;;;;;;;;;;KAUZ,qBAAqB,YAAY;cAEhC,aAAW,OAAA;;;;;;;;;KAMZ,qBAAqB,YAAY;cAEhC,eAAa,OAAA;;;KACd,uBAAuB,cAAc;KAErC,WAAW,cAAc,cAAc,cAAc;;cAGpD;UAEH;WACC;WACA;;;cAIE,SAAU,KAAK,KAAK,cAAY;;;;;cAUhC,QAAS,SAAS,YAAU;cAgB5B,aAAW,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cClEX,YAAU,OAAA;;;;;KAKX,oBAAoB,WAAW;cAE9B,eAAa,OAAA;;;;KAId,uBAAuB,cAAc;cAEpC,eAAa,OAAA;;;;;KAKd,uBAAuB,cAAc;cAEpC,WAAS,OAAA;;;;KAIV,mBAAmB,UAAU;KAE7B,WAAW,aAAa,gBAAgB,gBAAgB;;;;;cASvD,SAAU,SAAS;cAoBnB,cAAY,MAAA"}
|
package/dist/events/index.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { a as modalityOfKey, i as Modality, n as InputModalityMessage, r as InputModalityModel, t as InputModality } from "../input-modality-DGOhAknO.mjs";
|
|
1
2
|
import { Schema, Stream } from "effect";
|
|
2
3
|
import { Bundle } from "foldkit-bundle";
|
|
3
4
|
//#region src/events/active-element.d.ts
|
|
@@ -78,7 +79,14 @@ export type KeyboardMessage = typeof KeyboardMessage.Type;
|
|
|
78
79
|
* empty instead of throwing. Lift with `Subscription.persistent`, mapping
|
|
79
80
|
* into the parent's Message.
|
|
80
81
|
*/
|
|
81
|
-
export declare const keyboardEvents: (
|
|
82
|
+
export declare const keyboardEvents: (options?: {
|
|
83
|
+
/**
|
|
84
|
+
* Whether to cancel a press's default (the page scrolling under an arrow
|
|
85
|
+
* key, the browser's own shortcut), decided in the listener, since a
|
|
86
|
+
* default can only be cancelled before the event finishes dispatching.
|
|
87
|
+
*/
|
|
88
|
+
readonly preventDefault?: (press: KeyPress) => boolean;
|
|
89
|
+
}) => Stream.Stream<KeyboardMessage>;
|
|
82
90
|
//#endregion
|
|
83
91
|
//#region src/events/pointer.d.ts
|
|
84
92
|
export declare const PointerMessage: import("foldkit/message").MessageUnion<{
|
|
@@ -151,4 +159,5 @@ export declare const WindowSize: Bundle.Bundle<"WindowSize", void, {
|
|
|
151
159
|
readonly height: number;
|
|
152
160
|
}, never, never, never, void, {}, {}>;
|
|
153
161
|
//#endregion
|
|
162
|
+
export { InputModality, InputModalityMessage, InputModalityModel, Modality, modalityOfKey };
|
|
154
163
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -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":"
|
|
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;;;;;;YAGrB,8BAA8B,qBAAqB;;;;;;qBAOlD,2BAA0B,OAAO,OAAO;;;;;;;;;;;;;;;iBCNpC;WACN;WACA;WACA;WACA;WACA;WACA;;qBAkCE,cAAe,iBAAiB,OAAO;;;qBCvCvC,WAAS,OAAA;;;YACV,mBAAmB,UAAU;qBAE5B,uCAAW;;;;YACZ,qBAAqB,YAAY;qBAchC,MAAI,OAAA;;;;;;;;;;;qBCtBJ,2CAAe;;;;;;;;;;;;;YAWhB,yBAAyB,gBAAgB;;;;;;qBAOxC,iBACX;;;;;;WAMW,kBAAkB,OAAO;MAEnC,OAAO,OAAO;;;qBC5BJ,0CAAc;;;;;;YAGf,wBAAwB,eAAe;;;;;;qBAOtC,qBAAoB,OAAO,OAAO;;;qBCVlC,yCAAa;;;;;;YAGd,uBAAuB,cAAc;;;;;;;qBAQpC,oBAAmB,OAAO,OAAO;;;qBCTjC,iBAAe,OAAA;;;YAChB,yBAAyB,gBAAgB;qBAExC,6CAAiB;;sBAAjB,OAAA;;;YACD,2BAA2B,kBAAkB;qBAiB5C,YAAU,OAAA;;;;;;;;qBCnBV,iBAAe,OAAA;;;;YAChB,yBAAyB,gBAAgB;qBAExC,6CAAiB;;;;;;YAGlB,2BAA2B,kBAAkB;qBAmC5C,YAAU,OAAA"}
|
package/dist/events/index.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
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-m6v7__Xs.mjs";
|
|
1
3
|
import { Effect, Queue, Schema, Stream } from "effect";
|
|
2
4
|
import { defineMessageUnion } from "foldkit/message";
|
|
3
5
|
import * as Subscription from "foldkit/subscription";
|
|
@@ -103,14 +105,14 @@ const Idle = Bundle.make("Idle", {
|
|
|
103
105
|
args: Schema.Struct({ timeoutMs: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())) }),
|
|
104
106
|
init: () => ({ model: { idle: false } }),
|
|
105
107
|
update: (model, message) => IdleMessage.match(message, {
|
|
106
|
-
BecameIdle: () =>
|
|
108
|
+
BecameIdle: () => model.idle ? { model } : { model: {
|
|
107
109
|
...model,
|
|
108
110
|
idle: true
|
|
109
|
-
} }
|
|
110
|
-
BecameActive: () =>
|
|
111
|
+
} },
|
|
112
|
+
BecameActive: () => model.idle ? { model: {
|
|
111
113
|
...model,
|
|
112
114
|
idle: false
|
|
113
|
-
} }
|
|
115
|
+
} } : { model }
|
|
114
116
|
}),
|
|
115
117
|
subscriptions: ({ timeoutMs }) => Subscription.make()((entry) => ({ watch: entry({ idle: Schema.Boolean }, {
|
|
116
118
|
modelToDependencies: (model) => ({ idle: model.idle }),
|
|
@@ -141,18 +143,32 @@ const KeyboardMessage = defineMessageUnion({
|
|
|
141
143
|
* empty instead of throwing. Lift with `Subscription.persistent`, mapping
|
|
142
144
|
* into the parent's Message.
|
|
143
145
|
*/
|
|
144
|
-
const keyboardEvents = () => {
|
|
146
|
+
const keyboardEvents = (options = {}) => {
|
|
145
147
|
if (typeof window === "undefined") return Stream.empty;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
return Stream.callback((queue) => Effect.acquireRelease(Effect.sync(() => {
|
|
149
|
+
const down = (event) => {
|
|
150
|
+
const press = KeyboardMessage.Pressed({
|
|
151
|
+
key: event.key,
|
|
152
|
+
repeat: event.repeat,
|
|
153
|
+
ctrl: event.ctrlKey,
|
|
154
|
+
shift: event.shiftKey,
|
|
155
|
+
alt: event.altKey,
|
|
156
|
+
meta: event.metaKey
|
|
157
|
+
});
|
|
158
|
+
if (options.preventDefault?.(press) === true) event.preventDefault();
|
|
159
|
+
Queue.offerUnsafe(queue, press);
|
|
160
|
+
};
|
|
161
|
+
const up = (event) => Queue.offerUnsafe(queue, KeyboardMessage.Released({ key: event.key }));
|
|
162
|
+
window.addEventListener("keydown", down);
|
|
163
|
+
window.addEventListener("keyup", up);
|
|
164
|
+
return {
|
|
165
|
+
down,
|
|
166
|
+
up
|
|
167
|
+
};
|
|
168
|
+
}), ({ down, up }) => Effect.sync(() => {
|
|
169
|
+
window.removeEventListener("keydown", down);
|
|
170
|
+
window.removeEventListener("keyup", up);
|
|
153
171
|
})));
|
|
154
|
-
const ups = Stream.fromEventListener(window, "keyup").pipe(Stream.map((event) => KeyboardMessage.Released({ key: event.key })));
|
|
155
|
-
return Stream.mergeAll([downs, ups], { concurrency: "unbounded" });
|
|
156
172
|
};
|
|
157
173
|
//#endregion
|
|
158
174
|
//#region src/events/pointer.ts
|
|
@@ -222,7 +238,7 @@ const Visibility = Bundle.make("Visibility", {
|
|
|
222
238
|
Model: VisibilityModel,
|
|
223
239
|
Message: VisibilityMessage,
|
|
224
240
|
init: () => ({ model: { visible: isVisible() } }),
|
|
225
|
-
update: (model, message) =>
|
|
241
|
+
update: (model, message) => message.visible === model.visible ? { model } : { model: { visible: message.visible } },
|
|
226
242
|
subscriptions: () => Subscription.make()(() => ({ changes: Subscription.persistent(visibilityStream()) }))
|
|
227
243
|
});
|
|
228
244
|
//#endregion
|
|
@@ -275,13 +291,13 @@ const WindowSize = Bundle.make("WindowSize", {
|
|
|
275
291
|
width: 0,
|
|
276
292
|
height: 0
|
|
277
293
|
} }),
|
|
278
|
-
update: (model, message) => ({ model: {
|
|
294
|
+
update: (model, message) => ({ model: unlessSame(model, {
|
|
279
295
|
width: message.width,
|
|
280
296
|
height: message.height
|
|
281
|
-
} }),
|
|
297
|
+
}) }),
|
|
282
298
|
subscriptions: () => Subscription.make()(() => ({ changes: Subscription.persistent(sizeStream()) }))
|
|
283
299
|
});
|
|
284
300
|
//#endregion
|
|
285
|
-
export { ActiveElementMessage, Idle, IdleMessage, IdleModel, KeyboardMessage, PointerMessage, ScrollMessage, Visibility, VisibilityMessage, VisibilityModel, WindowSize, WindowSizeMessage, WindowSizeModel, activeElementEvents, keyboardEvents, matchHotkey, pointerEvents, scrollEvents };
|
|
301
|
+
export { ActiveElementMessage, Idle, IdleMessage, IdleModel, InputModality, InputModalityMessage, InputModalityModel, KeyboardMessage, Modality, PointerMessage, ScrollMessage, Visibility, VisibilityMessage, VisibilityModel, WindowSize, WindowSizeMessage, WindowSizeModel, activeElementEvents, keyboardEvents, matchHotkey, modalityOfKey, pointerEvents, scrollEvents };
|
|
286
302
|
|
|
287
303
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -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 BecameIdle: () => ({ model: { ...model, idle: true } }),\n BecameActive: () => ({ model: { ...model, idle: false } }),\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 { Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\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 = (): Stream.Stream<KeyboardMessage> => {\n if (typeof window === 'undefined') return Stream.empty\n const downs: Stream.Stream<KeyboardMessage> = Stream.fromEventListener<KeyboardEvent>(\n window,\n 'keydown',\n ).pipe(\n Stream.map(event =>\n 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 ),\n )\n const ups: Stream.Stream<KeyboardMessage> = Stream.fromEventListener<KeyboardEvent>(\n window,\n 'keyup',\n ).pipe(Stream.map(event => KeyboardMessage.Released({ key: event.key })))\n return Stream.mergeAll([downs, ups], { concurrency: 'unbounded' })\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 update: (model, message) => ({ 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'\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) => ({ model: { width: message.width, height: message.height } }),\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.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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"}
|