foldkit-primitives 0.1.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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +454 -0
  3. package/dist/device/index.d.mts +312 -0
  4. package/dist/device/index.d.mts.map +1 -0
  5. package/dist/device/index.mjs +490 -0
  6. package/dist/device/index.mjs.map +1 -0
  7. package/dist/dom/index.d.mts +73 -0
  8. package/dist/dom/index.d.mts.map +1 -0
  9. package/dist/dom/index.mjs +218 -0
  10. package/dist/dom/index.mjs.map +1 -0
  11. package/dist/events/index.d.mts +154 -0
  12. package/dist/events/index.d.mts.map +1 -0
  13. package/dist/events/index.mjs +287 -0
  14. package/dist/events/index.mjs.map +1 -0
  15. package/dist/index.d.mts +10 -0
  16. package/dist/index.mjs +10 -0
  17. package/dist/media/index.d.mts +79 -0
  18. package/dist/media/index.d.mts.map +1 -0
  19. package/dist/media/index.mjs +127 -0
  20. package/dist/media/index.mjs.map +1 -0
  21. package/dist/motion/index.d.mts +112 -0
  22. package/dist/motion/index.d.mts.map +1 -0
  23. package/dist/motion/index.mjs +208 -0
  24. package/dist/motion/index.mjs.map +1 -0
  25. package/dist/net/index.d.mts +199 -0
  26. package/dist/net/index.d.mts.map +1 -0
  27. package/dist/net/index.mjs +329 -0
  28. package/dist/net/index.mjs.map +1 -0
  29. package/dist/observers/index.d.mts +59 -0
  30. package/dist/observers/index.d.mts.map +1 -0
  31. package/dist/observers/index.mjs +156 -0
  32. package/dist/observers/index.mjs.map +1 -0
  33. package/dist/state/index.d.mts +342 -0
  34. package/dist/state/index.d.mts.map +1 -0
  35. package/dist/state/index.mjs +504 -0
  36. package/dist/state/index.mjs.map +1 -0
  37. package/dist/time/index.d.mts +129 -0
  38. package/dist/time/index.d.mts.map +1 -0
  39. package/dist/time/index.mjs +213 -0
  40. package/dist/time/index.mjs.map +1 -0
  41. package/package.json +96 -0
@@ -0,0 +1,218 @@
1
+ import { Effect, Queue, Schema, Stream } from "effect";
2
+ import * as Mount from "foldkit/mount";
3
+ import { defineMessageUnion } from "foldkit/message";
4
+ //#region src/dom/autofocus.ts
5
+ /**
6
+ * Autofocus as a Mount: focuses the element on insert, then emits `Focused`.
7
+ * The message means focus was requested — a non-focusable element or a
8
+ * hidden tab may decline, and the browser does not report that. Plain
9
+ * `focus()`, no scroll options: scroll behavior stays the application's
10
+ * choice.
11
+ */
12
+ const Focused = Schema.TaggedStruct("Focused", {});
13
+ const Autofocus = Mount.define("Autofocus", {
14
+ messages: [Focused],
15
+ execute: ({ element }) => Effect.sync(() => {
16
+ const focusable = element;
17
+ if (typeof focusable.focus === "function") focusable.focus.call(element);
18
+ return Focused.make({});
19
+ })
20
+ });
21
+ //#endregion
22
+ //#region src/dom/clipboard.ts
23
+ /**
24
+ * Clipboard copy as a Command: runs in `update` beside any bundle, yields
25
+ * `Copied` on success and `CopyFailed` otherwise. No Model involved: the
26
+ * clipboard is not application state.
27
+ */
28
+ const ClipboardMessage = defineMessageUnion({
29
+ Copied: {},
30
+ CopyFailed: { message: Schema.String }
31
+ });
32
+ const failMessage$2 = (error) => error instanceof Error ? error.message : String(error);
33
+ /**
34
+ * Copies text to the clipboard. Without a clipboard API (SSR, insecure
35
+ * context, denied permission) the command yields `CopyFailed` instead of
36
+ * throwing, so `update` handles both outcomes as Messages.
37
+ */
38
+ const copyText = (text) => ({
39
+ name: "Clipboard.copy",
40
+ args: { text },
41
+ effect: typeof navigator !== "undefined" && typeof navigator.clipboard?.writeText === "function" ? Effect.matchEffect(Effect.tryPromise({
42
+ try: () => navigator.clipboard.writeText(text),
43
+ catch: failMessage$2
44
+ }), {
45
+ onFailure: (message) => Effect.succeed(ClipboardMessage.CopyFailed({ message })),
46
+ onSuccess: () => Effect.succeed(ClipboardMessage.Copied())
47
+ }) : Effect.succeed(ClipboardMessage.CopyFailed({ message: "clipboard is unavailable" }))
48
+ });
49
+ //#endregion
50
+ //#region src/dom/input-mask.ts
51
+ /**
52
+ * An input mask as a Mount plus a pure mask function. The pattern language
53
+ * is three placeholders — `#` digit, `A` letter, `*` either, all ASCII —
54
+ * with every other character literal. Literals appear only once a later placeholder
55
+ * consumes input, so partial values never dangle a separator. The Mount
56
+ * rewrites the field, restores the caret approximately, and emits `Input`
57
+ * with both the masked and raw values; the parent owns the state, like any
58
+ * controlled input.
59
+ */
60
+ const Input = Schema.TaggedStruct("Input", {
61
+ value: Schema.String,
62
+ raw: Schema.String
63
+ });
64
+ const isDigit = (char) => char >= "0" && char <= "9";
65
+ const isLetter = (char) => char >= "a" && char <= "z" || char >= "A" && char <= "Z";
66
+ const matches = (placeholder, char) => placeholder === "#" ? isDigit(char) : placeholder === "A" ? isLetter(char) : isDigit(char) || isLetter(char);
67
+ const isPlaceholder = (char) => char === "#" || char === "A" || char === "*";
68
+ /** Whether any placeholder at or after `from` consumes any input at or after `at`. */
69
+ const fillable = (pattern, from, value, at) => {
70
+ for (let i = from; i < pattern.length; i++) {
71
+ const placeholder = pattern[i];
72
+ if (!isPlaceholder(placeholder)) continue;
73
+ for (let j = at; j < value.length; j++) if (matches(placeholder, value[j])) return true;
74
+ }
75
+ return false;
76
+ };
77
+ const applyMask = (value, pattern) => {
78
+ let masked = "";
79
+ let raw = "";
80
+ let at = 0;
81
+ for (let i = 0; i < pattern.length; i++) {
82
+ const slot = pattern[i];
83
+ if (isPlaceholder(slot)) {
84
+ while (at < value.length && !matches(slot, value[at])) at++;
85
+ if (at >= value.length) break;
86
+ masked += value[at];
87
+ raw += value[at];
88
+ at++;
89
+ } else if (fillable(pattern, i + 1, value, at)) masked += slot;
90
+ else break;
91
+ }
92
+ return {
93
+ masked,
94
+ raw
95
+ };
96
+ };
97
+ const asField = (element) => {
98
+ const field = element;
99
+ return typeof field.value === "string" && typeof field.addEventListener === "function" ? field : null;
100
+ };
101
+ const InputMask = Mount.defineStream("InputMask", {
102
+ messages: [Input],
103
+ args: { pattern: Schema.String },
104
+ execute: ({ element, pattern }) => Stream.callback((queue) => Effect.gen(function* () {
105
+ const field = asField(element);
106
+ if (field === null) return;
107
+ const onInput = () => {
108
+ const { masked, raw } = applyMask(field.value, pattern);
109
+ if (masked !== field.value) {
110
+ const caret = field.selectionStart ?? masked.length;
111
+ field.value = masked;
112
+ const at = Math.min(caret, masked.length);
113
+ try {
114
+ field.setSelectionRange?.(at, at);
115
+ } catch {}
116
+ }
117
+ Queue.offerUnsafe(queue, Input.make({
118
+ value: masked,
119
+ raw
120
+ }));
121
+ };
122
+ yield* Effect.acquireRelease(Effect.sync(() => {
123
+ field.addEventListener("input", onInput);
124
+ return field;
125
+ }), (current) => Effect.sync(() => current.removeEventListener("input", onInput)));
126
+ }))
127
+ });
128
+ //#endregion
129
+ //#region src/dom/script-loader.ts
130
+ /**
131
+ * Script loading as a Command: appends a `src` script to the head unless one
132
+ * is already there. Idempotent by URL — concurrent placements collapse onto
133
+ * the first tag instead of fetching twice. Yields `Loaded` on success and
134
+ * `LoadFailed` otherwise (network error, no document), so `update` handles
135
+ * both outcomes as Messages.
136
+ */
137
+ const ScriptMessage = defineMessageUnion({
138
+ Loaded: {},
139
+ LoadFailed: { message: Schema.String }
140
+ });
141
+ const failMessage$1 = (error) => error instanceof Error ? error.message : String(error);
142
+ const inflight = /* @__PURE__ */ new Map();
143
+ /** Loads a script by URL, once per URL. */
144
+ const loadScript = (src) => ({
145
+ name: "Script.load",
146
+ args: { src },
147
+ effect: Effect.matchEffect(Effect.tryPromise({
148
+ try: () => {
149
+ if (typeof document === "undefined") return Promise.reject(/* @__PURE__ */ new Error("script loading is unavailable"));
150
+ const running = inflight.get(src);
151
+ if (running !== void 0) return running;
152
+ if (Array.from(document.getElementsByTagName("script")).some((element) => element.getAttribute("src") === src)) return Promise.resolve();
153
+ const tracked = new Promise((resolve, reject) => {
154
+ const element = document.createElement("script");
155
+ element.src = src;
156
+ element.async = true;
157
+ element.onload = () => resolve();
158
+ element.onerror = () => {
159
+ element.remove();
160
+ reject(/* @__PURE__ */ new Error(`failed to load script: ${src}`));
161
+ };
162
+ document.head.appendChild(element);
163
+ }).then(() => {
164
+ inflight.delete(src);
165
+ }, (error) => {
166
+ inflight.delete(src);
167
+ throw error;
168
+ });
169
+ inflight.set(src, tracked);
170
+ return tracked;
171
+ },
172
+ catch: (error) => error
173
+ }), {
174
+ onFailure: (error) => Effect.succeed(ScriptMessage.LoadFailed({ message: failMessage$1(error) })),
175
+ onSuccess: () => Effect.succeed(ScriptMessage.Loaded())
176
+ })
177
+ });
178
+ //#endregion
179
+ //#region src/dom/share.ts
180
+ /**
181
+ * Web Share as a Command: runs in `update` beside any bundle, yields
182
+ * `Shared` on success. Dismissal is its own outcome, not a failure — the
183
+ * application tells a cancelled sheet apart from a broken one. Without a
184
+ * share API (desktop, insecure context) it yields `ShareFailed` instead of
185
+ * throwing, so `update` handles every outcome as a Message.
186
+ */
187
+ const ShareData = Schema.Struct({
188
+ title: Schema.optional(Schema.String),
189
+ text: Schema.optional(Schema.String),
190
+ url: Schema.optional(Schema.String)
191
+ });
192
+ const ShareMessage = defineMessageUnion({
193
+ Shared: {},
194
+ Dismissed: {},
195
+ ShareFailed: { message: Schema.String }
196
+ });
197
+ const failMessage = (error) => error instanceof Error ? error.message : String(error);
198
+ const isDismissal = (error) => typeof error?.name === "string" && error.name === "AbortError";
199
+ /** Shares data through the platform sheet. */
200
+ const share = (data) => ({
201
+ name: "Share.share",
202
+ args: { data },
203
+ effect: typeof navigator !== "undefined" && typeof navigator.share === "function" ? Effect.matchEffect(Effect.tryPromise({
204
+ try: () => navigator.share({
205
+ ...data.title !== void 0 ? { title: data.title } : {},
206
+ ...data.text !== void 0 ? { text: data.text } : {},
207
+ ...data.url !== void 0 ? { url: data.url } : {}
208
+ }),
209
+ catch: (error) => error
210
+ }), {
211
+ onFailure: (error) => Effect.succeed(isDismissal(error) ? ShareMessage.Dismissed() : ShareMessage.ShareFailed({ message: failMessage(error) })),
212
+ onSuccess: () => Effect.succeed(ShareMessage.Shared())
213
+ }) : Effect.succeed(ShareMessage.ShareFailed({ message: "share is unavailable" }))
214
+ });
215
+ //#endregion
216
+ export { Autofocus, ClipboardMessage, Focused, Input, InputMask, ScriptMessage, ShareData, ShareMessage, applyMask, copyText, loadScript, share };
217
+
218
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["failMessage","failMessage"],"sources":["../../src/dom/autofocus.ts","../../src/dom/clipboard.ts","../../src/dom/input-mask.ts","../../src/dom/script-loader.ts","../../src/dom/share.ts"],"sourcesContent":["/**\n * Autofocus as a Mount: focuses the element on insert, then emits `Focused`.\n * The message means focus was requested — a non-focusable element or a\n * hidden tab may decline, and the browser does not report that. Plain\n * `focus()`, no scroll options: scroll behavior stays the application's\n * choice.\n */\nimport { Effect, Schema } from 'effect'\nimport * as Mount from 'foldkit/mount'\n\nexport const Focused = Schema.TaggedStruct('Focused', {})\nexport type Focused = typeof Focused.Type\n\nexport const Autofocus = Mount.define('Autofocus', {\n messages: [Focused],\n execute: ({ element }) =>\n Effect.sync(() => {\n const focusable = element as unknown as { readonly focus?: unknown }\n if (typeof focusable.focus === 'function') {\n ;(focusable.focus as () => void).call(element)\n }\n return Focused.make({})\n }),\n})\n","/**\n * Clipboard copy as a Command: runs in `update` beside any bundle, yields\n * `Copied` on success and `CopyFailed` otherwise. No Model involved: the\n * clipboard is not application state.\n */\nimport { Effect, Schema } from 'effect'\nimport type { Command } from 'foldkit/command'\nimport { defineMessageUnion } from 'foldkit/message'\n\nexport const ClipboardMessage = defineMessageUnion({\n Copied: {},\n CopyFailed: { message: Schema.String },\n})\nexport type ClipboardMessage = typeof ClipboardMessage.Type\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\n/**\n * Copies text to the clipboard. Without a clipboard API (SSR, insecure\n * context, denied permission) the command yields `CopyFailed` instead of\n * throwing, so `update` handles both outcomes as Messages.\n */\nexport const copyText = (text: string): Command<ClipboardMessage, never, never> => ({\n name: 'Clipboard.copy',\n args: { text },\n effect:\n typeof navigator !== 'undefined' && typeof navigator.clipboard?.writeText === 'function'\n ? Effect.matchEffect(\n Effect.tryPromise({\n try: () => navigator.clipboard.writeText(text),\n catch: failMessage,\n }),\n {\n onFailure: message => Effect.succeed(ClipboardMessage.CopyFailed({ message })),\n onSuccess: () => Effect.succeed(ClipboardMessage.Copied()),\n },\n )\n : Effect.succeed(ClipboardMessage.CopyFailed({ message: 'clipboard is unavailable' })),\n})\n","/**\n * An input mask as a Mount plus a pure mask function. The pattern language\n * is three placeholders — `#` digit, `A` letter, `*` either, all ASCII —\n * with every other character literal. Literals appear only once a later placeholder\n * consumes input, so partial values never dangle a separator. The Mount\n * rewrites the field, restores the caret approximately, and emits `Input`\n * with both the masked and raw values; the parent owns the state, like any\n * controlled input.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport * as Mount from 'foldkit/mount'\n\nexport const Input = Schema.TaggedStruct('Input', {\n value: Schema.String,\n raw: Schema.String,\n})\nexport type Input = typeof Input.Type\n\nconst isDigit = (char: string): boolean => char >= '0' && char <= '9'\nconst isLetter = (char: string): boolean =>\n (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z')\n\nconst matches = (placeholder: string, char: string): boolean =>\n placeholder === '#'\n ? isDigit(char)\n : placeholder === 'A'\n ? isLetter(char)\n : isDigit(char) || isLetter(char)\n\nconst isPlaceholder = (char: string): boolean => char === '#' || char === 'A' || char === '*'\n\n/** Whether any placeholder at or after `from` consumes any input at or after `at`. */\nconst fillable = (pattern: string, from: number, value: string, at: number): boolean => {\n for (let i = from; i < pattern.length; i++) {\n const placeholder = pattern[i]!\n if (!isPlaceholder(placeholder)) continue\n for (let j = at; j < value.length; j++) {\n if (matches(placeholder, value[j]!)) return true\n }\n }\n return false\n}\n\nexport const applyMask = (\n value: string,\n pattern: string,\n): { readonly masked: string; readonly raw: string } => {\n let masked = ''\n let raw = ''\n let at = 0\n for (let i = 0; i < pattern.length; i++) {\n const slot = pattern[i]!\n if (isPlaceholder(slot)) {\n while (at < value.length && !matches(slot, value[at]!)) at++\n if (at >= value.length) break\n masked += value[at]!\n raw += value[at]!\n at++\n } else if (fillable(pattern, i + 1, value, at)) {\n masked += slot\n } else {\n break\n }\n }\n return { masked, raw }\n}\n\ninterface MaskableField {\n value: string\n selectionStart: number | null\n addEventListener(type: string, listener: () => void): void\n removeEventListener(type: string, listener: () => void): void\n setSelectionRange?(start: number, end: number): void\n}\n\nconst asField = (element: Element): MaskableField | null => {\n const field = element as unknown as Partial<MaskableField>\n return typeof field.value === 'string' && typeof field.addEventListener === 'function'\n ? (field as MaskableField)\n : null\n}\n\nexport const InputMask = Mount.defineStream('InputMask', {\n messages: [Input],\n args: { pattern: Schema.String },\n execute: ({ element, pattern }) =>\n Stream.callback<typeof Input.Type>(queue =>\n Effect.gen(function* () {\n const field = asField(element)\n if (field === null) return\n const onInput = () => {\n const { masked, raw } = applyMask(field.value, pattern)\n if (masked !== field.value) {\n const caret = field.selectionStart ?? masked.length\n field.value = masked\n const at = Math.min(caret, masked.length)\n try {\n field.setSelectionRange?.(at, at)\n } catch {\n // Select-all style inputs reject ranges; the value still stands.\n }\n }\n Queue.offerUnsafe(queue, Input.make({ value: masked, raw }))\n }\n yield* Effect.acquireRelease(\n Effect.sync(() => {\n field.addEventListener('input', onInput)\n return field\n }),\n current => Effect.sync(() => current.removeEventListener('input', onInput)),\n )\n }),\n ),\n})\n","/**\n * Script loading as a Command: appends a `src` script to the head unless one\n * is already there. Idempotent by URL — concurrent placements collapse onto\n * the first tag instead of fetching twice. Yields `Loaded` on success and\n * `LoadFailed` otherwise (network error, no document), so `update` handles\n * both outcomes as Messages.\n */\nimport { Effect, Schema } from 'effect'\nimport type { Command } from 'foldkit/command'\nimport { defineMessageUnion } from 'foldkit/message'\n\nexport const ScriptMessage = defineMessageUnion({\n Loaded: {},\n LoadFailed: { message: Schema.String },\n})\nexport type ScriptMessage = typeof ScriptMessage.Type\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\n// Tags still fetching, by URL: concurrent loads share one tag and its fate.\nconst inflight = new Map<string, Promise<void>>()\n\n/** Loads a script by URL, once per URL. */\nexport const loadScript = (src: string): Command<ScriptMessage, never, never> => ({\n name: 'Script.load',\n args: { src },\n effect: Effect.matchEffect(\n Effect.tryPromise({\n try: () => {\n if (typeof document === 'undefined') {\n return Promise.reject(new Error('script loading is unavailable'))\n }\n // Concurrent loads share one tag and its fate, instead of racing\n // past the presence check and fetching twice: the in-flight entry\n // is checked before presence, in the same synchronous block that\n // appends, so a second load cannot slip between them.\n const running = inflight.get(src)\n if (running !== undefined) return running\n // getAttribute, not .src: the property resolves to an absolute URL\n // and would never equal the given string.\n const present = Array.from(document.getElementsByTagName('script')).some(\n element => element.getAttribute('src') === src,\n )\n if (present) return Promise.resolve()\n const completion = new Promise<void>((resolve, reject) => {\n const element = document.createElement('script')\n element.src = src\n element.async = true\n element.onload = () => resolve()\n element.onerror = () => {\n // A dead tag must not satisfy later presence checks.\n element.remove()\n reject(new Error(`failed to load script: ${src}`))\n }\n document.head.appendChild(element)\n })\n const tracked = completion.then(\n () => {\n inflight.delete(src)\n },\n (error: unknown) => {\n inflight.delete(src)\n throw error\n },\n )\n inflight.set(src, tracked)\n return tracked\n },\n catch: (error: unknown) => error,\n }),\n {\n onFailure: error => Effect.succeed(ScriptMessage.LoadFailed({ message: failMessage(error) })),\n onSuccess: () => Effect.succeed(ScriptMessage.Loaded()),\n },\n ),\n})\n","/**\n * Web Share as a Command: runs in `update` beside any bundle, yields\n * `Shared` on success. Dismissal is its own outcome, not a failure — the\n * application tells a cancelled sheet apart from a broken one. Without a\n * share API (desktop, insecure context) it yields `ShareFailed` instead of\n * throwing, so `update` handles every outcome as a Message.\n */\nimport { Effect, Schema } from 'effect'\nimport type { Command } from 'foldkit/command'\nimport { defineMessageUnion } from 'foldkit/message'\n\nexport const ShareData = Schema.Struct({\n title: Schema.optional(Schema.String),\n text: Schema.optional(Schema.String),\n url: Schema.optional(Schema.String),\n})\nexport type ShareData = typeof ShareData.Type\n\nexport const ShareMessage = defineMessageUnion({\n Shared: {},\n Dismissed: {},\n ShareFailed: { message: Schema.String },\n})\nexport type ShareMessage = typeof ShareMessage.Type\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nconst isDismissal = (error: unknown): boolean =>\n typeof (error as { readonly name?: unknown } | null)?.name === 'string' &&\n (error as { readonly name: string }).name === 'AbortError'\n\n/** Shares data through the platform sheet. */\nexport const share = (data: ShareData): Command<ShareMessage, never, never> => ({\n name: 'Share.share',\n args: { data },\n effect:\n typeof navigator !== 'undefined' && typeof navigator.share === 'function'\n ? Effect.matchEffect(\n Effect.tryPromise({\n // Omit absent fields: the platform type rejects explicit\n // undefined under exactOptionalPropertyTypes.\n try: () =>\n navigator.share({\n ...(data.title !== undefined ? { title: data.title } : {}),\n ...(data.text !== undefined ? { text: data.text } : {}),\n ...(data.url !== undefined ? { url: data.url } : {}),\n }),\n catch: (error: unknown) => error,\n }),\n {\n onFailure: error =>\n Effect.succeed(\n isDismissal(error)\n ? ShareMessage.Dismissed()\n : ShareMessage.ShareFailed({ message: failMessage(error) }),\n ),\n onSuccess: () => Effect.succeed(ShareMessage.Shared()),\n },\n )\n : Effect.succeed(ShareMessage.ShareFailed({ message: 'share is unavailable' })),\n})\n"],"mappings":"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"}
@@ -0,0 +1,154 @@
1
+ import { Schema, Stream } from "effect";
2
+ import { Bundle } from "foldkit-bundle";
3
+ //#region src/events/active-element.d.ts
4
+ export declare const ActiveElementMessage: import("foldkit/message").MessageUnion<{
5
+ readonly Changed: {
6
+ readonly tag: Schema.String;
7
+ readonly id: Schema.NullOr<Schema.String>;
8
+ };
9
+ }>;
10
+ export type ActiveElementMessage = typeof ActiveElementMessage.Type;
11
+ /**
12
+ * Focus changes on the document. Without a document the stream is empty
13
+ * instead of throwing. Lift with `Subscription.persistent`, mapping into
14
+ * the parent's Message.
15
+ */
16
+ export declare const activeElementEvents: () => Stream.Stream<ActiveElementMessage>;
17
+ //#endregion
18
+ //#region src/events/hotkeys.d.ts
19
+ /**
20
+ * Hotkey matching as a pure function over a `Pressed` payload: `matchHotkey`
21
+ * answers whether a press is the shortcut, so `update` stays a table of
22
+ * chords instead of a nest of modifier checks. Patterns read like
23
+ * `"ctrl+shift+k"`: modifiers first (any order), the key last,
24
+ * case-insensitive, with `cmd`/`command` and `option` as the Mac names for
25
+ * meta and alt. Matching is exact — unlisted modifiers must be up — and
26
+ * auto-repeat never matches, so holding a chord fires once. `space` (or a
27
+ * bare space) means the space key; `esc` means escape. A pattern with
28
+ * no key, or two keys, matches nothing: predicates return false, they do
29
+ * not throw on the hot path.
30
+ */
31
+ export interface KeyPress {
32
+ readonly key: string;
33
+ readonly repeat: boolean;
34
+ readonly ctrl: boolean;
35
+ readonly shift: boolean;
36
+ readonly alt: boolean;
37
+ readonly meta: boolean;
38
+ }
39
+ export declare const matchHotkey: (pattern: string, press: KeyPress) => boolean;
40
+ //#endregion
41
+ //#region src/events/idle.d.ts
42
+ export declare const IdleModel: Schema.Struct<{
43
+ readonly idle: Schema.Boolean;
44
+ }>;
45
+ export type IdleModel = typeof IdleModel.Type;
46
+ export declare const IdleMessage: import("foldkit/message").MessageUnion<{
47
+ readonly BecameIdle: {};
48
+ readonly BecameActive: {};
49
+ }>;
50
+ export type IdleMessage = typeof IdleMessage.Type;
51
+ export declare const Idle: Bundle.Bundle<"Idle", {
52
+ readonly timeoutMs: number;
53
+ }, {
54
+ idle: boolean;
55
+ }, {
56
+ readonly _tag: "BecameIdle";
57
+ } | {
58
+ readonly _tag: "BecameActive";
59
+ }, never, never, never, void, {}, {}>;
60
+ //#endregion
61
+ //#region src/events/keyboard.d.ts
62
+ export declare const KeyboardMessage: import("foldkit/message").MessageUnion<{
63
+ readonly Pressed: {
64
+ readonly key: Schema.String;
65
+ readonly repeat: Schema.Boolean;
66
+ readonly ctrl: Schema.Boolean;
67
+ readonly shift: Schema.Boolean;
68
+ readonly alt: Schema.Boolean;
69
+ readonly meta: Schema.Boolean;
70
+ };
71
+ readonly Released: {
72
+ readonly key: Schema.String;
73
+ };
74
+ }>;
75
+ export type KeyboardMessage = typeof KeyboardMessage.Type;
76
+ /**
77
+ * Key presses and releases on the window. Without a window the stream is
78
+ * empty instead of throwing. Lift with `Subscription.persistent`, mapping
79
+ * into the parent's Message.
80
+ */
81
+ export declare const keyboardEvents: () => Stream.Stream<KeyboardMessage>;
82
+ //#endregion
83
+ //#region src/events/pointer.d.ts
84
+ export declare const PointerMessage: import("foldkit/message").MessageUnion<{
85
+ readonly Moved: {
86
+ readonly x: Schema.Number;
87
+ readonly y: Schema.Number;
88
+ };
89
+ }>;
90
+ export type PointerMessage = typeof PointerMessage.Type;
91
+ /**
92
+ * Pointer moves on the window. Without a window the stream is empty instead
93
+ * of throwing. Lift with `Subscription.persistent`, mapping into the
94
+ * parent's Message.
95
+ */
96
+ export declare const pointerEvents: () => Stream.Stream<PointerMessage>;
97
+ //#endregion
98
+ //#region src/events/scroll.d.ts
99
+ export declare const ScrollMessage: import("foldkit/message").MessageUnion<{
100
+ readonly Scrolled: {
101
+ readonly x: Schema.Number;
102
+ readonly y: Schema.Number;
103
+ };
104
+ }>;
105
+ export type ScrollMessage = typeof ScrollMessage.Type;
106
+ /**
107
+ * Scrolls on the window, in the capture phase so container scrolls (which
108
+ * do not bubble) report too. Without a window the stream is empty instead
109
+ * of throwing. Lift with `Subscription.persistent`, mapping into the
110
+ * parent's Message.
111
+ */
112
+ export declare const scrollEvents: () => Stream.Stream<ScrollMessage>;
113
+ //#endregion
114
+ //#region src/events/visibility.d.ts
115
+ export declare const VisibilityModel: Schema.Struct<{
116
+ readonly visible: Schema.Boolean;
117
+ }>;
118
+ export type VisibilityModel = typeof VisibilityModel.Type;
119
+ export declare const VisibilityMessage: import("foldkit/message").MessageUnion<{
120
+ readonly Changed: {
121
+ readonly visible: Schema.Boolean;
122
+ };
123
+ }>;
124
+ export type VisibilityMessage = typeof VisibilityMessage.Type;
125
+ export declare const Visibility: Bundle.Bundle<"Visibility", void, {
126
+ visible: boolean;
127
+ }, {
128
+ readonly _tag: "Changed";
129
+ readonly visible: boolean;
130
+ }, never, never, never, void, {}, {}>;
131
+ //#endregion
132
+ //#region src/events/window-size.d.ts
133
+ export declare const WindowSizeModel: Schema.Struct<{
134
+ readonly width: Schema.Number;
135
+ readonly height: Schema.Number;
136
+ }>;
137
+ export type WindowSizeModel = typeof WindowSizeModel.Type;
138
+ export declare const WindowSizeMessage: import("foldkit/message").MessageUnion<{
139
+ readonly Changed: {
140
+ readonly width: Schema.Number;
141
+ readonly height: Schema.Number;
142
+ };
143
+ }>;
144
+ export type WindowSizeMessage = typeof WindowSizeMessage.Type;
145
+ export declare const WindowSize: Bundle.Bundle<"WindowSize", void, {
146
+ width: number;
147
+ height: number;
148
+ }, {
149
+ readonly _tag: "Changed";
150
+ readonly width: number;
151
+ readonly height: number;
152
+ }, never, never, never, void, {}, {}>;
153
+ //#endregion
154
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +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;;;;;;YAGrB,8BAA8B,qBAAqB;;;;;;qBAOlD,2BAA0B,OAAO,OAAO;;;;;;;;;;;;;;;iBCNpC;WACN;WACA;WACA;WACA;WACA;WACA;;qBAkCE,cAAW,iBAAmB,OAAS;;;qBCvCvC,WAAS,OAAA;;;YACV,mBAAmB,UAAU;qBAE5B,uCAAW;;;;YACZ,qBAAqB,YAAY;qBAchC,MAAI,OAAA;;;;;;;;;;;qBCvBJ,2CAAe;;;;;;;;;;;;;YAWhB,yBAAyB,gBAAgB;;;;;;qBAOxC,sBAAqB,OAAO,OAAO;;;qBClBnC,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;;;;;;;;qBCpBV,iBAAe,OAAA;;;;YAChB,yBAAyB,gBAAgB;qBAExC,6CAAiB;;;;;;YAGlB,2BAA2B,kBAAkB;qBAmC5C,YAAU,OAAA"}