foldkit 0.18.0 → 0.20.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 (85) hide show
  1. package/README.md +14 -14
  2. package/dist/command/index.d.ts +4 -0
  3. package/dist/command/index.d.ts.map +1 -0
  4. package/dist/command/index.js +1 -0
  5. package/dist/command/public.d.ts +2 -0
  6. package/dist/command/public.d.ts.map +1 -0
  7. package/dist/command/public.js +1 -0
  8. package/dist/effectExtensions/optionExtensions.d.ts +4 -0
  9. package/dist/effectExtensions/optionExtensions.d.ts.map +1 -1
  10. package/dist/effectExtensions/optionExtensions.js +2 -1
  11. package/dist/fieldValidation/index.d.ts.map +1 -1
  12. package/dist/fieldValidation/index.js +4 -4
  13. package/dist/html/index.d.ts +45 -3
  14. package/dist/html/index.d.ts.map +1 -1
  15. package/dist/html/index.js +53 -7
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -0
  19. package/dist/message/index.d.ts +2 -0
  20. package/dist/message/index.d.ts.map +1 -0
  21. package/dist/message/index.js +1 -0
  22. package/dist/message/public.d.ts +2 -0
  23. package/dist/message/public.d.ts.map +1 -0
  24. package/dist/message/public.js +1 -0
  25. package/dist/route/index.d.ts +1 -0
  26. package/dist/route/index.d.ts.map +1 -1
  27. package/dist/route/index.js +1 -0
  28. package/dist/route/parser.js +17 -17
  29. package/dist/route/public.d.ts +1 -1
  30. package/dist/route/public.d.ts.map +1 -1
  31. package/dist/route/public.js +1 -1
  32. package/dist/runtime/public.d.ts +1 -1
  33. package/dist/runtime/public.d.ts.map +1 -1
  34. package/dist/runtime/runtime.d.ts +2 -3
  35. package/dist/runtime/runtime.d.ts.map +1 -1
  36. package/dist/runtime/runtime.js +9 -9
  37. package/dist/schema/index.d.ts +36 -8
  38. package/dist/schema/index.d.ts.map +1 -1
  39. package/dist/schema/index.js +6 -0
  40. package/dist/task/dom.d.ts +58 -0
  41. package/dist/task/dom.d.ts.map +1 -0
  42. package/dist/task/dom.js +112 -0
  43. package/dist/task/error.d.ts +18 -0
  44. package/dist/task/error.d.ts.map +1 -0
  45. package/dist/task/error.js +7 -0
  46. package/dist/task/index.d.ts +7 -108
  47. package/dist/task/index.d.ts.map +1 -1
  48. package/dist/task/index.js +7 -168
  49. package/dist/task/inert.d.ts +26 -0
  50. package/dist/task/inert.d.ts.map +1 -0
  51. package/dist/task/inert.js +87 -0
  52. package/dist/task/public.d.ts +1 -1
  53. package/dist/task/public.d.ts.map +1 -1
  54. package/dist/task/public.js +1 -1
  55. package/dist/task/random.d.ts +11 -0
  56. package/dist/task/random.d.ts.map +1 -0
  57. package/dist/task/random.js +10 -0
  58. package/dist/task/scrollLock.d.ts +24 -0
  59. package/dist/task/scrollLock.d.ts.map +1 -0
  60. package/dist/task/scrollLock.js +46 -0
  61. package/dist/task/time.d.ts +43 -0
  62. package/dist/task/time.d.ts.map +1 -0
  63. package/dist/task/time.js +53 -0
  64. package/dist/task/timing.d.ts +35 -0
  65. package/dist/task/timing.d.ts.map +1 -0
  66. package/dist/task/timing.js +51 -0
  67. package/dist/ui/dialog/index.d.ts +1 -1
  68. package/dist/ui/dialog/index.d.ts.map +1 -1
  69. package/dist/ui/dialog/index.js +7 -7
  70. package/dist/ui/disclosure/index.d.ts +1 -1
  71. package/dist/ui/disclosure/index.d.ts.map +1 -1
  72. package/dist/ui/disclosure/index.js +7 -7
  73. package/dist/ui/keyboard.d.ts.map +1 -1
  74. package/dist/ui/keyboard.js +1 -1
  75. package/dist/ui/menu/index.d.ts +71 -18
  76. package/dist/ui/menu/index.d.ts.map +1 -1
  77. package/dist/ui/menu/index.js +325 -113
  78. package/dist/ui/menu/public.d.ts +2 -2
  79. package/dist/ui/menu/public.d.ts.map +1 -1
  80. package/dist/ui/menu/public.js +1 -1
  81. package/dist/ui/tabs/index.d.ts +4 -5
  82. package/dist/ui/tabs/index.d.ts.map +1 -1
  83. package/dist/ui/tabs/index.js +12 -14
  84. package/dist/url/index.js +4 -4
  85. package/package.json +13 -1
@@ -1,109 +1,8 @@
1
- import { DateTime, Duration, Effect } from 'effect';
2
- /**
3
- * Creates a command that gets the current UTC time and passes it to a message constructor.
4
- * This is similar to Elm's `Task.perform` with `Time.now`.
5
- *
6
- * @example
7
- * ```typescript
8
- * Task.getTime(utc => GotTime({ utc }))
9
- * ```
10
- */
11
- export declare const getTime: <Message>(f: (utc: DateTime.Utc) => Message) => Effect.Effect<Message>;
12
- /**
13
- * Creates a command that gets the system timezone and passes it to a message constructor.
14
- * This is similar to Elm's `Task.perform` with `Time.here`.
15
- *
16
- * @example
17
- * ```typescript
18
- * Task.getTimeZone(zone => GotTimeZone({ zone }))
19
- * ```
20
- */
21
- export declare const getTimeZone: <Message>(f: (zone: DateTime.TimeZone) => Message) => Effect.Effect<Message>;
22
- /**
23
- * Creates a command that gets the current time in the system timezone and passes it to a message constructor.
24
- * This combines both time and timezone in a single task.
25
- *
26
- * @example
27
- * ```typescript
28
- * Task.getZonedTime(zoned => GotTime({ zoned }))
29
- * ```
30
- */
31
- export declare const getZonedTime: <Message>(f: (zoned: DateTime.Zoned) => Message) => Effect.Effect<Message>;
32
- /**
33
- * Creates a command that gets the current time in a specific timezone and passes it to a message constructor.
34
- * If the timezone is invalid, the effect will fail with an error string.
35
- *
36
- * @example
37
- * ```typescript
38
- * Task.getZonedTimeIn('America/New_York', zoned => GotNYTime({ zoned }))
39
- * ```
40
- */
41
- export declare const getZonedTimeIn: <Message>(zoneId: string, f: (zoned: DateTime.Zoned) => Message) => Effect.Effect<Message, string>;
42
- /**
43
- * Creates a command that focuses an element by selector and passes the result to a message constructor.
44
- * Passes true if the element was found and focused, false otherwise.
45
- * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to focus.
46
- * This follows the same approach as Elm's Browser.Dom.focus.
47
- *
48
- * @example
49
- * ```typescript
50
- * Task.focus('#email-input', success => InputFocused({ success }))
51
- * ```
52
- */
53
- export declare const focus: <Message>(selector: string, f: (success: boolean) => Message) => Effect.Effect<Message>;
54
- /**
55
- * Creates a command that opens a dialog element as a modal using `showModal()`.
56
- * Passes true if the element was found and opened, false otherwise.
57
- * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to show.
58
- *
59
- * @example
60
- * ```typescript
61
- * Task.showModal('#my-dialog', success => ModalOpened({ success }))
62
- * ```
63
- */
64
- export declare const showModal: <Message>(selector: string, f: (success: boolean) => Message) => Effect.Effect<Message>;
65
- /**
66
- * Creates a command that closes a dialog element using `.close()`.
67
- * Passes true if the element was found and closed, false otherwise.
68
- * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to close.
69
- *
70
- * @example
71
- * ```typescript
72
- * Task.closeModal('#my-dialog', success => ModalClosed({ success }))
73
- * ```
74
- */
75
- export declare const closeModal: <Message>(selector: string, f: (success: boolean) => Message) => Effect.Effect<Message>;
76
- /**
77
- * Creates a command that resolves to a message after a delay.
78
- * Useful for debouncing, such as clearing a typeahead search query.
79
- *
80
- * @example
81
- * ```typescript
82
- * Task.delay(350, () => SearchCleared({ version: model.searchVersion }))
83
- * Task.delay(Duration.seconds(1), () => TimedOut())
84
- * ```
85
- */
86
- export declare const delay: <Message>(duration: Duration.DurationInput, f: () => Message) => Effect.Effect<Message>;
87
- /**
88
- * Creates a command that generates a random integer between min (inclusive) and max (exclusive)
89
- * and passes it to a message constructor.
90
- *
91
- * @example
92
- * ```typescript
93
- * Task.randomInt(0, 100, value => GotRandom({ value }))
94
- * ```
95
- */
96
- /**
97
- * Creates a command that scrolls an element into view by selector and passes the result to a message constructor.
98
- * Passes true if the element was found and scrolled, false otherwise.
99
- * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to scroll.
100
- * Uses `{ block: 'nearest' }` to avoid unnecessary scrolling when the element is already visible.
101
- *
102
- * @example
103
- * ```typescript
104
- * Task.scrollIntoView('#active-item', success => ItemScrolled({ success }))
105
- * ```
106
- */
107
- export declare const scrollIntoView: <Message>(selector: string, f: (success: boolean) => Message) => Effect.Effect<Message>;
108
- export declare const randomInt: <Message>(min: number, max: number, f: (value: number) => Message) => Effect.Effect<Message>;
1
+ export { ElementNotFound, TimeZoneError } from './error';
2
+ export { getTime, getTimeZone, getZonedTime, getZonedTimeIn } from './time';
3
+ export { focus, showModal, closeModal, clickElement, scrollIntoView, } from './dom';
4
+ export { delay, nextFrame, waitForTransitions } from './timing';
5
+ export { randomInt } from './random';
6
+ export { lockScroll, unlockScroll } from './scrollLock';
7
+ export { inertOthers, restoreInert } from './inert';
109
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/task/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAA;AAE3D;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,OAAO,EAC7B,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,GAAG,KAAK,OAAO,KAChC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAgC,CAAA;AAExD;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,EACjC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,QAAQ,KAAK,OAAO,KACtC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAmD,CAAA;AAE3E;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,EAClC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,KAAK,OAAO,KACpC,MAAM,CAAC,MAAM,CAAC,OAAO,CAMpB,CAAA;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,EACpC,QAAQ,MAAM,EACd,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,KAAK,OAAO,KACpC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAS5B,CAAA;AAEJ;;;;;;;;;;GAUG;AACH,eAAO,MAAM,KAAK,GAAI,OAAO,EAC3B,UAAU,MAAM,EAChB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,KAC/B,MAAM,CAAC,MAAM,CAAC,OAAO,CAWpB,CAAA;AAEJ;;;;;;;;;GASG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO,EAC/B,UAAU,MAAM,EAChB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,KAC/B,MAAM,CAAC,MAAM,CAAC,OAAO,CAWpB,CAAA;AAEJ;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO,EAChC,UAAU,MAAM,EAChB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,KAC/B,MAAM,CAAC,MAAM,CAAC,OAAO,CAWpB,CAAA;AAEJ;;;;;;;;;GASG;AACH,eAAO,MAAM,KAAK,GAAI,OAAO,EAC3B,UAAU,QAAQ,CAAC,aAAa,EAChC,GAAG,MAAM,OAAO,KACf,MAAM,CAAC,MAAM,CAAC,OAAO,CAIpB,CAAA;AAEJ;;;;;;;;GAQG;AACH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,EACpC,UAAU,MAAM,EAChB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,KAC/B,MAAM,CAAC,MAAM,CAAC,OAAO,CAWpB,CAAA;AAEJ,eAAO,MAAM,SAAS,GAAI,OAAO,EAC/B,KAAK,MAAM,EACX,KAAK,MAAM,EACX,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,KAC5B,MAAM,CAAC,MAAM,CAAC,OAAO,CAC6C,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/task/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACxD,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAC3E,OAAO,EACL,KAAK,EACL,SAAS,EACT,UAAU,EACV,YAAY,EACZ,cAAc,GACf,MAAM,OAAO,CAAA;AACd,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA"}
@@ -1,168 +1,7 @@
1
- import { DateTime, Effect, Option } from 'effect';
2
- /**
3
- * Creates a command that gets the current UTC time and passes it to a message constructor.
4
- * This is similar to Elm's `Task.perform` with `Time.now`.
5
- *
6
- * @example
7
- * ```typescript
8
- * Task.getTime(utc => GotTime({ utc }))
9
- * ```
10
- */
11
- export const getTime = (f) => Effect.map(DateTime.now, f);
12
- /**
13
- * Creates a command that gets the system timezone and passes it to a message constructor.
14
- * This is similar to Elm's `Task.perform` with `Time.here`.
15
- *
16
- * @example
17
- * ```typescript
18
- * Task.getTimeZone(zone => GotTimeZone({ zone }))
19
- * ```
20
- */
21
- export const getTimeZone = (f) => Effect.sync(() => f(DateTime.zoneMakeLocal()));
22
- /**
23
- * Creates a command that gets the current time in the system timezone and passes it to a message constructor.
24
- * This combines both time and timezone in a single task.
25
- *
26
- * @example
27
- * ```typescript
28
- * Task.getZonedTime(zoned => GotTime({ zoned }))
29
- * ```
30
- */
31
- export const getZonedTime = (f) => Effect.gen(function* () {
32
- const utc = yield* DateTime.now;
33
- const zone = DateTime.zoneMakeLocal();
34
- const zoned = DateTime.setZone(utc, zone);
35
- return f(zoned);
36
- });
37
- /**
38
- * Creates a command that gets the current time in a specific timezone and passes it to a message constructor.
39
- * If the timezone is invalid, the effect will fail with an error string.
40
- *
41
- * @example
42
- * ```typescript
43
- * Task.getZonedTimeIn('America/New_York', zoned => GotNYTime({ zoned }))
44
- * ```
45
- */
46
- export const getZonedTimeIn = (zoneId, f) => Effect.gen(function* () {
47
- const utc = yield* DateTime.now;
48
- const maybeZone = DateTime.zoneMakeNamed(zoneId);
49
- if (Option.isNone(maybeZone)) {
50
- return yield* Effect.fail(`Invalid timezone: ${zoneId}`);
51
- }
52
- const zoned = DateTime.setZone(utc, maybeZone.value);
53
- return f(zoned);
54
- });
55
- /**
56
- * Creates a command that focuses an element by selector and passes the result to a message constructor.
57
- * Passes true if the element was found and focused, false otherwise.
58
- * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to focus.
59
- * This follows the same approach as Elm's Browser.Dom.focus.
60
- *
61
- * @example
62
- * ```typescript
63
- * Task.focus('#email-input', success => InputFocused({ success }))
64
- * ```
65
- */
66
- export const focus = (selector, f) => Effect.async((resume) => {
67
- requestAnimationFrame(() => {
68
- const element = document.querySelector(selector);
69
- if (element instanceof HTMLElement) {
70
- element.focus();
71
- resume(Effect.succeed(f(true)));
72
- }
73
- else {
74
- resume(Effect.succeed(f(false)));
75
- }
76
- });
77
- });
78
- /**
79
- * Creates a command that opens a dialog element as a modal using `showModal()`.
80
- * Passes true if the element was found and opened, false otherwise.
81
- * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to show.
82
- *
83
- * @example
84
- * ```typescript
85
- * Task.showModal('#my-dialog', success => ModalOpened({ success }))
86
- * ```
87
- */
88
- export const showModal = (selector, f) => Effect.async((resume) => {
89
- requestAnimationFrame(() => {
90
- const element = document.querySelector(selector);
91
- if (element instanceof HTMLDialogElement) {
92
- element.showModal();
93
- resume(Effect.succeed(f(true)));
94
- }
95
- else {
96
- resume(Effect.succeed(f(false)));
97
- }
98
- });
99
- });
100
- /**
101
- * Creates a command that closes a dialog element using `.close()`.
102
- * Passes true if the element was found and closed, false otherwise.
103
- * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to close.
104
- *
105
- * @example
106
- * ```typescript
107
- * Task.closeModal('#my-dialog', success => ModalClosed({ success }))
108
- * ```
109
- */
110
- export const closeModal = (selector, f) => Effect.async((resume) => {
111
- requestAnimationFrame(() => {
112
- const element = document.querySelector(selector);
113
- if (element instanceof HTMLDialogElement) {
114
- element.close();
115
- resume(Effect.succeed(f(true)));
116
- }
117
- else {
118
- resume(Effect.succeed(f(false)));
119
- }
120
- });
121
- });
122
- /**
123
- * Creates a command that resolves to a message after a delay.
124
- * Useful for debouncing, such as clearing a typeahead search query.
125
- *
126
- * @example
127
- * ```typescript
128
- * Task.delay(350, () => SearchCleared({ version: model.searchVersion }))
129
- * Task.delay(Duration.seconds(1), () => TimedOut())
130
- * ```
131
- */
132
- export const delay = (duration, f) => Effect.gen(function* () {
133
- yield* Effect.sleep(duration);
134
- return f();
135
- });
136
- /**
137
- * Creates a command that generates a random integer between min (inclusive) and max (exclusive)
138
- * and passes it to a message constructor.
139
- *
140
- * @example
141
- * ```typescript
142
- * Task.randomInt(0, 100, value => GotRandom({ value }))
143
- * ```
144
- */
145
- /**
146
- * Creates a command that scrolls an element into view by selector and passes the result to a message constructor.
147
- * Passes true if the element was found and scrolled, false otherwise.
148
- * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to scroll.
149
- * Uses `{ block: 'nearest' }` to avoid unnecessary scrolling when the element is already visible.
150
- *
151
- * @example
152
- * ```typescript
153
- * Task.scrollIntoView('#active-item', success => ItemScrolled({ success }))
154
- * ```
155
- */
156
- export const scrollIntoView = (selector, f) => Effect.async((resume) => {
157
- requestAnimationFrame(() => {
158
- const element = document.querySelector(selector);
159
- if (element instanceof HTMLElement) {
160
- element.scrollIntoView({ block: 'nearest' });
161
- resume(Effect.succeed(f(true)));
162
- }
163
- else {
164
- resume(Effect.succeed(f(false)));
165
- }
166
- });
167
- });
168
- export const randomInt = (min, max, f) => Effect.sync(() => f(Math.floor(Math.random() * (max - min)) + min));
1
+ export { ElementNotFound, TimeZoneError } from './error';
2
+ export { getTime, getTimeZone, getZonedTime, getZonedTimeIn } from './time';
3
+ export { focus, showModal, closeModal, clickElement, scrollIntoView, } from './dom';
4
+ export { delay, nextFrame, waitForTransitions } from './timing';
5
+ export { randomInt } from './random';
6
+ export { lockScroll, unlockScroll } from './scrollLock';
7
+ export { inertOthers, restoreInert } from './inert';
@@ -0,0 +1,26 @@
1
+ import { Effect } from 'effect';
2
+ /**
3
+ * Marks all DOM elements outside the given selectors as `inert` and
4
+ * `aria-hidden="true"`. Walks each allowed element up to `document.body`,
5
+ * marking siblings that don't contain an allowed element. Uses reference
6
+ * counting so nested calls are safe.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * Task.inertOthers('my-menu', ['#menu-button', '#menu-items']).pipe(
11
+ * Effect.as(NoOp()),
12
+ * )
13
+ * ```
14
+ */
15
+ export declare const inertOthers: (id: string, allowedSelectors: ReadonlyArray<string>) => Effect.Effect<void>;
16
+ /**
17
+ * Restores all elements previously marked inert by `inertOthers` for the
18
+ * given ID. Safe to call without a preceding `inertOthers` — acts as a no-op.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * Task.restoreInert('my-menu').pipe(Effect.as(NoOp()))
23
+ * ```
24
+ */
25
+ export declare const restoreInert: (id: string) => Effect.Effect<void>;
26
+ //# sourceMappingURL=inert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inert.d.ts","sourceRoot":"","sources":["../../src/task/inert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAAmC,MAAM,QAAQ,CAAA;AAoFvE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GACtB,IAAI,MAAM,EACV,kBAAkB,aAAa,CAAC,MAAM,CAAC,KACtC,MAAM,CAAC,MAAM,CAAC,IAAI,CAajB,CAAA;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GAAI,IAAI,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAQxD,CAAA"}
@@ -0,0 +1,87 @@
1
+ import { Array, Effect, Number, Option, Predicate, pipe } from 'effect';
2
+ const inertState = {
3
+ originals: new Map(),
4
+ counts: new Map(),
5
+ cleanups: new Map(),
6
+ };
7
+ const markInert = (element) => {
8
+ const count = inertState.counts.get(element) ?? 0;
9
+ inertState.counts.set(element, Number.increment(count));
10
+ if (count === 0) {
11
+ inertState.originals.set(element, {
12
+ ariaHidden: element.getAttribute('aria-hidden'),
13
+ inert: element.inert,
14
+ });
15
+ element.setAttribute('aria-hidden', 'true');
16
+ element.inert = true;
17
+ }
18
+ return () => markNotInert(element);
19
+ };
20
+ const markNotInert = (element) => {
21
+ const count = inertState.counts.get(element) ?? 1;
22
+ if (count === 1) {
23
+ const original = inertState.originals.get(element);
24
+ if (original) {
25
+ if (Predicate.isNull(original.ariaHidden)) {
26
+ element.removeAttribute('aria-hidden');
27
+ }
28
+ else {
29
+ element.setAttribute('aria-hidden', original.ariaHidden);
30
+ }
31
+ element.inert = original.inert;
32
+ inertState.originals.delete(element);
33
+ }
34
+ inertState.counts.delete(element);
35
+ }
36
+ else {
37
+ inertState.counts.set(element, Number.decrement(count));
38
+ }
39
+ };
40
+ const resolveElements = (selectors) => Array.filterMap(selectors, selector => {
41
+ const element = document.querySelector(selector);
42
+ return element instanceof HTMLElement ? Option.some(element) : Option.none();
43
+ });
44
+ const ancestorsUpToBody = (element) => Array.unfold(element.parentElement, current => Predicate.isNotNull(current)
45
+ ? Option.some([
46
+ current,
47
+ current === document.body ? null : current.parentElement,
48
+ ])
49
+ : Option.none());
50
+ const inertableSiblings = (parent, allowedElements) => pipe(parent.children, Array.fromIterable, Array.filterMap(child => child instanceof HTMLElement &&
51
+ !Array.some(allowedElements, allowed => child.contains(allowed))
52
+ ? Option.some(child)
53
+ : Option.none()));
54
+ /**
55
+ * Marks all DOM elements outside the given selectors as `inert` and
56
+ * `aria-hidden="true"`. Walks each allowed element up to `document.body`,
57
+ * marking siblings that don't contain an allowed element. Uses reference
58
+ * counting so nested calls are safe.
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * Task.inertOthers('my-menu', ['#menu-button', '#menu-items']).pipe(
63
+ * Effect.as(NoOp()),
64
+ * )
65
+ * ```
66
+ */
67
+ export const inertOthers = (id, allowedSelectors) => Effect.sync(() => {
68
+ const allowedElements = resolveElements(allowedSelectors);
69
+ const cleanupFunctions = pipe(allowedElements, Array.flatMap(ancestorsUpToBody), Array.flatMap(ancestor => Array.map(inertableSiblings(ancestor, allowedElements), markInert)));
70
+ inertState.cleanups.set(id, cleanupFunctions);
71
+ });
72
+ /**
73
+ * Restores all elements previously marked inert by `inertOthers` for the
74
+ * given ID. Safe to call without a preceding `inertOthers` — acts as a no-op.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * Task.restoreInert('my-menu').pipe(Effect.as(NoOp()))
79
+ * ```
80
+ */
81
+ export const restoreInert = (id) => Effect.sync(() => {
82
+ const cleanupFunctions = inertState.cleanups.get(id);
83
+ if (cleanupFunctions) {
84
+ Array.forEach(cleanupFunctions, cleanup => cleanup());
85
+ inertState.cleanups.delete(id);
86
+ }
87
+ });
@@ -1,2 +1,2 @@
1
- export { getTime, getTimeZone, getZonedTime, getZonedTimeIn, focus, showModal, closeModal, delay, scrollIntoView, randomInt, } from './index';
1
+ export { ElementNotFound, TimeZoneError, getTime, getTimeZone, getZonedTime, getZonedTimeIn, focus, showModal, closeModal, clickElement, delay, scrollIntoView, randomInt, nextFrame, waitForTransitions, } from './index';
2
2
  //# sourceMappingURL=public.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/task/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,WAAW,EACX,YAAY,EACZ,cAAc,EACd,KAAK,EACL,SAAS,EACT,UAAU,EACV,KAAK,EACL,cAAc,EACd,SAAS,GACV,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/task/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,aAAa,EACb,OAAO,EACP,WAAW,EACX,YAAY,EACZ,cAAc,EACd,KAAK,EACL,SAAS,EACT,UAAU,EACV,YAAY,EACZ,KAAK,EACL,cAAc,EACd,SAAS,EACT,SAAS,EACT,kBAAkB,GACnB,MAAM,SAAS,CAAA"}
@@ -1 +1 @@
1
- export { getTime, getTimeZone, getZonedTime, getZonedTimeIn, focus, showModal, closeModal, delay, scrollIntoView, randomInt, } from './index';
1
+ export { ElementNotFound, TimeZoneError, getTime, getTimeZone, getZonedTime, getZonedTimeIn, focus, showModal, closeModal, clickElement, delay, scrollIntoView, randomInt, nextFrame, waitForTransitions, } from './index';
@@ -0,0 +1,11 @@
1
+ import { Effect } from 'effect';
2
+ /**
3
+ * Generates a random integer between min (inclusive) and max (exclusive).
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * Task.randomInt(1, 7).pipe(Effect.map(value => GotDiceRoll({ value })))
8
+ * ```
9
+ */
10
+ export declare const randomInt: (min: number, max: number) => Effect.Effect<number>;
11
+ //# sourceMappingURL=random.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"random.d.ts","sourceRoot":"","sources":["../../src/task/random.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,GAAI,KAAK,MAAM,EAAE,KAAK,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CACP,CAAA"}
@@ -0,0 +1,10 @@
1
+ import { Effect } from 'effect';
2
+ /**
3
+ * Generates a random integer between min (inclusive) and max (exclusive).
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * Task.randomInt(1, 7).pipe(Effect.map(value => GotDiceRoll({ value })))
8
+ * ```
9
+ */
10
+ export const randomInt = (min, max) => Effect.sync(() => Math.floor(Math.random() * (max - min)) + min);
@@ -0,0 +1,24 @@
1
+ import { Effect } from 'effect';
2
+ /**
3
+ * Locks page scroll by setting `overflow: hidden` on the document element.
4
+ * Compensates for scrollbar width with padding to prevent layout shift.
5
+ * Uses reference counting so nested locks are safe — the page only unlocks
6
+ * when every lock has been released.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * Task.lockScroll.pipe(Effect.as(NoOp()))
11
+ * ```
12
+ */
13
+ export declare const lockScroll: Effect.Effect<void>;
14
+ /**
15
+ * Releases one scroll lock. When the last lock is released, restores the
16
+ * original `overflow` and `padding-right` on the document element.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * Task.unlockScroll.pipe(Effect.as(NoOp()))
21
+ * ```
22
+ */
23
+ export declare const unlockScroll: Effect.Effect<void>;
24
+ //# sourceMappingURL=scrollLock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrollLock.d.ts","sourceRoot":"","sources":["../../src/task/scrollLock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAA;AAQvC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAkBzC,CAAA;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAU3C,CAAA"}
@@ -0,0 +1,46 @@
1
+ import { Effect, Number } from 'effect';
2
+ const scrollLockState = {
3
+ count: 0,
4
+ overflow: '',
5
+ paddingRight: '',
6
+ };
7
+ /**
8
+ * Locks page scroll by setting `overflow: hidden` on the document element.
9
+ * Compensates for scrollbar width with padding to prevent layout shift.
10
+ * Uses reference counting so nested locks are safe — the page only unlocks
11
+ * when every lock has been released.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * Task.lockScroll.pipe(Effect.as(NoOp()))
16
+ * ```
17
+ */
18
+ export const lockScroll = Effect.sync(() => {
19
+ const { documentElement, documentElement: { style }, } = document;
20
+ if (scrollLockState.count === 0) {
21
+ scrollLockState.overflow = style.overflow;
22
+ scrollLockState.paddingRight = style.paddingRight;
23
+ const scrollbarWidth = window.innerWidth - documentElement.clientWidth;
24
+ style.overflow = 'hidden';
25
+ style.paddingRight =
26
+ scrollbarWidth > 0 ? `${scrollbarWidth}px` : style.paddingRight;
27
+ }
28
+ scrollLockState.count++;
29
+ });
30
+ /**
31
+ * Releases one scroll lock. When the last lock is released, restores the
32
+ * original `overflow` and `padding-right` on the document element.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * Task.unlockScroll.pipe(Effect.as(NoOp()))
37
+ * ```
38
+ */
39
+ export const unlockScroll = Effect.sync(() => {
40
+ scrollLockState.count = Math.max(0, Number.decrement(scrollLockState.count));
41
+ if (scrollLockState.count === 0) {
42
+ const { documentElement: { style }, } = document;
43
+ style.overflow = scrollLockState.overflow;
44
+ style.paddingRight = scrollLockState.paddingRight;
45
+ }
46
+ });
@@ -0,0 +1,43 @@
1
+ import { DateTime, Effect } from 'effect';
2
+ import { TimeZoneError } from './error';
3
+ /**
4
+ * Gets the current UTC time.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * Task.getTime.pipe(Effect.map(utc => GotTime({ utc })))
9
+ * ```
10
+ */
11
+ export declare const getTime: Effect.Effect<DateTime.Utc>;
12
+ /**
13
+ * Gets the system timezone.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * Task.getTimeZone.pipe(Effect.map(zone => GotTimeZone({ zone })))
18
+ * ```
19
+ */
20
+ export declare const getTimeZone: Effect.Effect<DateTime.TimeZone>;
21
+ /**
22
+ * Gets the current time in the system timezone.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * Task.getZonedTime.pipe(Effect.map(zoned => GotTime({ zoned })))
27
+ * ```
28
+ */
29
+ export declare const getZonedTime: Effect.Effect<DateTime.Zoned>;
30
+ /**
31
+ * Gets the current time in a specific timezone.
32
+ * Fails with `TimeZoneError` if the timezone ID is invalid.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * Task.getZonedTimeIn('America/New_York').pipe(
37
+ * Effect.map(zoned => GotNYTime({ zoned })),
38
+ * Effect.catchAll(() => Effect.succeed(FailedTimeZone())),
39
+ * )
40
+ * ```
41
+ */
42
+ export declare const getZonedTimeIn: (zoneId: string) => Effect.Effect<DateTime.Zoned, TimeZoneError>;
43
+ //# sourceMappingURL=time.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/task/time.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAA;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAEvC;;;;;;;GAOG;AACH,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAgB,CAAA;AAEhE;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAExD,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAMtD,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,GACzB,QAAQ,MAAM,KACb,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAQ1C,CAAA"}
@@ -0,0 +1,53 @@
1
+ import { DateTime, Effect, Option } from 'effect';
2
+ import { TimeZoneError } from './error';
3
+ /**
4
+ * Gets the current UTC time.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * Task.getTime.pipe(Effect.map(utc => GotTime({ utc })))
9
+ * ```
10
+ */
11
+ export const getTime = DateTime.now;
12
+ /**
13
+ * Gets the system timezone.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * Task.getTimeZone.pipe(Effect.map(zone => GotTimeZone({ zone })))
18
+ * ```
19
+ */
20
+ export const getTimeZone = Effect.sync(() => DateTime.zoneMakeLocal());
21
+ /**
22
+ * Gets the current time in the system timezone.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * Task.getZonedTime.pipe(Effect.map(zoned => GotTime({ zoned })))
27
+ * ```
28
+ */
29
+ export const getZonedTime = Effect.gen(function* () {
30
+ const utc = yield* DateTime.now;
31
+ const zone = DateTime.zoneMakeLocal();
32
+ return DateTime.setZone(utc, zone);
33
+ });
34
+ /**
35
+ * Gets the current time in a specific timezone.
36
+ * Fails with `TimeZoneError` if the timezone ID is invalid.
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * Task.getZonedTimeIn('America/New_York').pipe(
41
+ * Effect.map(zoned => GotNYTime({ zoned })),
42
+ * Effect.catchAll(() => Effect.succeed(FailedTimeZone())),
43
+ * )
44
+ * ```
45
+ */
46
+ export const getZonedTimeIn = (zoneId) => Effect.gen(function* () {
47
+ const utc = yield* DateTime.now;
48
+ const maybeZone = DateTime.zoneMakeNamed(zoneId);
49
+ if (Option.isNone(maybeZone)) {
50
+ return yield* Effect.fail(new TimeZoneError({ zoneId }));
51
+ }
52
+ return DateTime.setZone(utc, maybeZone.value);
53
+ });