foldkit 0.18.0 → 0.19.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 (69) hide show
  1. package/README.md +11 -11
  2. package/dist/effectExtensions/optionExtensions.d.ts +4 -0
  3. package/dist/effectExtensions/optionExtensions.d.ts.map +1 -1
  4. package/dist/effectExtensions/optionExtensions.js +2 -1
  5. package/dist/fieldValidation/index.d.ts.map +1 -1
  6. package/dist/fieldValidation/index.js +4 -4
  7. package/dist/html/index.d.ts +31 -3
  8. package/dist/html/index.d.ts.map +1 -1
  9. package/dist/html/index.js +49 -7
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1 -0
  13. package/dist/message/index.d.ts +2 -0
  14. package/dist/message/index.d.ts.map +1 -0
  15. package/dist/message/index.js +1 -0
  16. package/dist/message/public.d.ts +2 -0
  17. package/dist/message/public.d.ts.map +1 -0
  18. package/dist/message/public.js +1 -0
  19. package/dist/route/index.d.ts +1 -0
  20. package/dist/route/index.d.ts.map +1 -1
  21. package/dist/route/index.js +1 -0
  22. package/dist/route/parser.js +17 -17
  23. package/dist/route/public.d.ts +1 -1
  24. package/dist/route/public.d.ts.map +1 -1
  25. package/dist/route/public.js +1 -1
  26. package/dist/runtime/runtime.js +8 -8
  27. package/dist/schema/index.d.ts +36 -8
  28. package/dist/schema/index.d.ts.map +1 -1
  29. package/dist/schema/index.js +6 -0
  30. package/dist/task/dom.d.ts +59 -0
  31. package/dist/task/dom.d.ts.map +1 -0
  32. package/dist/task/dom.js +113 -0
  33. package/dist/task/index.d.ts +6 -108
  34. package/dist/task/index.d.ts.map +1 -1
  35. package/dist/task/index.js +6 -168
  36. package/dist/task/inert.d.ts +25 -0
  37. package/dist/task/inert.d.ts.map +1 -0
  38. package/dist/task/inert.js +88 -0
  39. package/dist/task/public.d.ts +1 -1
  40. package/dist/task/public.d.ts.map +1 -1
  41. package/dist/task/public.js +1 -1
  42. package/dist/task/random.d.ts +12 -0
  43. package/dist/task/random.d.ts.map +1 -0
  44. package/dist/task/random.js +11 -0
  45. package/dist/task/scrollLock.d.ts +24 -0
  46. package/dist/task/scrollLock.d.ts.map +1 -0
  47. package/dist/task/scrollLock.js +48 -0
  48. package/dist/task/time.d.ts +42 -0
  49. package/dist/task/time.d.ts.map +1 -0
  50. package/dist/task/time.js +54 -0
  51. package/dist/task/timing.d.ts +36 -0
  52. package/dist/task/timing.d.ts.map +1 -0
  53. package/dist/task/timing.js +55 -0
  54. package/dist/ui/dialog/index.d.ts.map +1 -1
  55. package/dist/ui/dialog/index.js +4 -4
  56. package/dist/ui/disclosure/index.d.ts.map +1 -1
  57. package/dist/ui/disclosure/index.js +4 -4
  58. package/dist/ui/keyboard.d.ts.map +1 -1
  59. package/dist/ui/keyboard.js +1 -1
  60. package/dist/ui/menu/index.d.ts +70 -17
  61. package/dist/ui/menu/index.d.ts.map +1 -1
  62. package/dist/ui/menu/index.js +317 -112
  63. package/dist/ui/menu/public.d.ts +2 -2
  64. package/dist/ui/menu/public.d.ts.map +1 -1
  65. package/dist/ui/menu/public.js +1 -1
  66. package/dist/ui/tabs/index.d.ts.map +1 -1
  67. package/dist/ui/tabs/index.js +6 -6
  68. package/dist/url/index.js +4 -4
  69. package/package.json +5 -1
@@ -23,11 +23,11 @@ const makeRuntime = ({ Model, Flags: _Flags, flags: flags_, init, update, view,
23
23
  const [initModel, initCommands] = Predicate.isNotUndefined(hmrModel)
24
24
  ? pipe(hmrModel, Schema.decodeUnknownEither(Model), Either.match({
25
25
  onLeft: () => init(flags, Option.getOrUndefined(currentUrl)),
26
- onRight: (restoredModel) => [restoredModel, []],
26
+ onRight: restoredModel => [restoredModel, []],
27
27
  }))
28
28
  : init(flags, Option.getOrUndefined(currentUrl));
29
29
  const modelSubscriptionRef = yield* SubscriptionRef.make(initModel);
30
- yield* Effect.forEach(initCommands, (command) => Effect.forkDaemon(command.pipe(Effect.flatMap(enqueueMessage))));
30
+ yield* Effect.forEach(initCommands, command => Effect.forkDaemon(command.pipe(Effect.flatMap(enqueueMessage))));
31
31
  if (browserConfig) {
32
32
  addNavigationEventListeners(messageQueue, browserConfig);
33
33
  }
@@ -43,7 +43,7 @@ const makeRuntime = ({ Model, Flags: _Flags, flags: flags_, init, update, view,
43
43
  yield* SubscriptionRef.set(modelSubscriptionRef, nextModel);
44
44
  preserveModel(nextModel);
45
45
  }
46
- yield* Effect.forEach(commands, (command) => Effect.forkDaemon(command.pipe(Effect.flatMap(enqueueMessage))));
46
+ yield* Effect.forEach(commands, command => Effect.forkDaemon(command.pipe(Effect.flatMap(enqueueMessage))));
47
47
  });
48
48
  const runProcessMessage = (messageEffect) => (runtime) => {
49
49
  try {
@@ -69,7 +69,7 @@ const makeRuntime = ({ Model, Flags: _Flags, flags: flags_, init, update, view,
69
69
  const dispatchAsync = (message) =>
70
70
  /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
71
71
  enqueueMessage(message);
72
- const render = (model) => view(model).pipe(Effect.flatMap((nextVNodeNullish) => Effect.gen(function* () {
72
+ const render = (model) => view(model).pipe(Effect.flatMap(nextVNodeNullish => Effect.gen(function* () {
73
73
  const maybeCurrentVNode = yield* Ref.get(maybeCurrentVNodeRef);
74
74
  const patchedVNode = yield* Effect.sync(() => patchVNode(maybeCurrentVNode, nextVNodeNullish, container));
75
75
  yield* Ref.set(maybeCurrentVNodeRef, Option.some(patchedVNode));
@@ -93,7 +93,7 @@ const makeRuntime = ({ Model, Flags: _Flags, flags: flags_, init, update, view,
93
93
  yield* pipe(Effect.forever(Effect.gen(function* () {
94
94
  const message = yield* Queue.take(messageQueue);
95
95
  yield* processMessage(message);
96
- })), Effect.catchAllCause((cause) => Effect.sync(() => {
96
+ })), Effect.catchAllCause(cause => Effect.sync(() => {
97
97
  const squashed = Cause.squash(cause);
98
98
  const appError = squashed instanceof Error ? squashed : new Error(String(squashed));
99
99
  renderErrorView(appError, errorView, container, maybeCurrentVNodeRef);
@@ -105,7 +105,7 @@ const patchVNode = (maybeCurrentVNode, nextVNodeNullish, container) => {
105
105
  : h('#text', {}, '');
106
106
  return Option.match(maybeCurrentVNode, {
107
107
  onNone: () => patch(toVNode(container), nextVNode),
108
- onSome: (currentVNode) => patch(currentVNode, nextVNode),
108
+ onSome: currentVNode => patch(currentVNode, nextVNode),
109
109
  });
110
110
  };
111
111
  const renderErrorView = (appError, errorView, container, maybeCurrentVNodeRef) => {
@@ -140,7 +140,7 @@ export function makeElement(config) {
140
140
  ...baseConfig,
141
141
  Flags: config.Flags,
142
142
  flags: config.flags,
143
- init: (flags) => config.init(flags),
143
+ init: flags => config.init(flags),
144
144
  });
145
145
  }
146
146
  else {
@@ -188,7 +188,7 @@ const preserveModel = (model) => {
188
188
  /** Starts a Foldkit runtime, with HMR support for development. */
189
189
  export const run = (foldkitRuntime) => {
190
190
  if (import.meta.hot) {
191
- import.meta.hot.on('foldkit:restore-model', (model) => {
191
+ import.meta.hot.on('foldkit:restore-model', model => {
192
192
  BrowserRuntime.runMain(foldkitRuntime(model));
193
193
  });
194
194
  import.meta.hot.send('foldkit:request-model');
@@ -6,19 +6,47 @@ export type CallableTaggedStruct<Tag extends string, Fields extends S.Struct.Fie
6
6
  readonly _tag: S.tag<Tag>;
7
7
  } & Fields>>);
8
8
  /**
9
- * A wrapper around Effect Schema.taggedStruct that returns a callable schema.
9
+ * Wraps `Schema.TaggedStruct` to create a message variant you can call directly as a constructor.
10
+ * Use `m` for message types — enabling `ClickedReset()` instead of `ClickedReset.make()`.
10
11
  *
11
- * Abbreviated as `ts` because it's used so frequently throughout Foldkit applications.
12
+ * @example
13
+ * ```typescript
14
+ * const ClickedReset = m('ClickedReset')
15
+ * ClickedReset() // { _tag: 'ClickedReset' }
16
+ *
17
+ * const ChangedCount = m('ChangedCount', { count: S.Number })
18
+ * ChangedCount({ count: 1 }) // { _tag: 'ChangedCount', count: 1 }
19
+ * ```
20
+ */
21
+ export declare function m<Tag extends string>(tag: Tag): CallableTaggedStruct<Tag, {}>;
22
+ export declare function m<Tag extends string, Fields extends S.Struct.Fields>(tag: Tag, fields: Fields): CallableTaggedStruct<Tag, Fields>;
23
+ /**
24
+ * Wraps `Schema.TaggedStruct` to create a route variant you can call directly as a constructor.
25
+ * Use `r` for route types — enabling `Home()` instead of `Home.make()`.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const Home = r('Home')
30
+ * Home() // { _tag: 'Home' }
31
+ *
32
+ * const UserProfile = r('UserProfile', { id: S.String })
33
+ * UserProfile({ id: 'abc' }) // { _tag: 'UserProfile', id: 'abc' }
34
+ * ```
35
+ */
36
+ export declare function r<Tag extends string>(tag: Tag): CallableTaggedStruct<Tag, {}>;
37
+ export declare function r<Tag extends string, Fields extends S.Struct.Fields>(tag: Tag, fields: Fields): CallableTaggedStruct<Tag, Fields>;
38
+ /**
39
+ * Wraps `Schema.TaggedStruct` to create a callable tagged struct you can call directly as a constructor.
40
+ * Use `ts` for non-message, non-route tagged structs — enabling `Loading()`
41
+ * instead of `Loading.make()`.
12
42
  *
13
43
  * @example
14
44
  * ```typescript
15
- * // Simple tag — callable with no args
16
- * const Reset = ts('Reset')
17
- * Reset() // { _tag: 'Reset' }
45
+ * const Loading = ts('Loading')
46
+ * Loading() // { _tag: 'Loading' }
18
47
  *
19
- * // Tag with fields callable with fields
20
- * const SetCount = ts('SetCount', { count: S.Number })
21
- * SetCount({ count: 1 }) // { _tag: 'SetCount', count: 1 }
48
+ * const Ok = ts('Ok', { data: S.String })
49
+ * Ok({ data: 'hello' }) // { _tag: 'Ok', data: 'hello' }
22
50
  * ```
23
51
  */
24
52
  export declare function ts<Tag extends string>(tag: Tag): CallableTaggedStruct<Tag, {}>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AAEpC,qIAAqI;AACrI,MAAM,MAAM,oBAAoB,CAC9B,GAAG,SAAS,MAAM,EAClB,MAAM,SAAS,CAAC,CAAC,MAAM,CAAC,MAAM,IAC5B,CAAC,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,GAC7B,CAAC,MAAM,MAAM,SAAS,KAAK,GACvB,CACE,KAAK,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,KAC9D,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;CAAE,GAAG,MAAM,CAAC,CAAC,GACtE,CACE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KACtD,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;CAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;AAe7E;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,EAAE,CAAC,GAAG,SAAS,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,oBAAoB,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;AAC/E,wBAAgB,EAAE,CAAC,GAAG,SAAS,MAAM,EAAE,MAAM,SAAS,CAAC,CAAC,MAAM,CAAC,MAAM,EACnE,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,MAAM,GACb,oBAAoB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AAEpC,qIAAqI;AACrI,MAAM,MAAM,oBAAoB,CAC9B,GAAG,SAAS,MAAM,EAClB,MAAM,SAAS,CAAC,CAAC,MAAM,CAAC,MAAM,IAC5B,CAAC,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,GAC7B,CAAC,MAAM,MAAM,SAAS,KAAK,GACvB,CACE,KAAK,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,KAC9D,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;CAAE,GAAG,MAAM,CAAC,CAAC,GACtE,CACE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KACtD,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;CAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;AAe7E;;;;;;;;;;;;GAYG;AACH,wBAAgB,CAAC,CAAC,GAAG,SAAS,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,oBAAoB,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;AAC9E,wBAAgB,CAAC,CAAC,GAAG,SAAS,MAAM,EAAE,MAAM,SAAS,CAAC,CAAC,MAAM,CAAC,MAAM,EAClE,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,MAAM,GACb,oBAAoB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;AAKpC;;;;;;;;;;;;GAYG;AACH,wBAAgB,CAAC,CAAC,GAAG,SAAS,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,oBAAoB,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;AAC9E,wBAAgB,CAAC,CAAC,GAAG,SAAS,MAAM,EAAE,MAAM,SAAS,CAAC,CAAC,MAAM,CAAC,MAAM,EAClE,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,MAAM,GACb,oBAAoB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;AAKpC;;;;;;;;;;;;;GAaG;AACH,wBAAgB,EAAE,CAAC,GAAG,SAAS,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,oBAAoB,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;AAC/E,wBAAgB,EAAE,CAAC,GAAG,SAAS,MAAM,EAAE,MAAM,SAAS,CAAC,CAAC,MAAM,CAAC,MAAM,EACnE,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,MAAM,GACb,oBAAoB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA"}
@@ -9,6 +9,12 @@ new Proxy(schema, {
9
9
  return Reflect.get(target, property, receiver);
10
10
  },
11
11
  });
12
+ export function m(tag, fields = {}) {
13
+ return makeCallable(S.TaggedStruct(tag, fields));
14
+ }
15
+ export function r(tag, fields = {}) {
16
+ return makeCallable(S.TaggedStruct(tag, fields));
17
+ }
12
18
  export function ts(tag, fields = {}) {
13
19
  return makeCallable(S.TaggedStruct(tag, fields));
14
20
  }
@@ -0,0 +1,59 @@
1
+ import { Effect } from 'effect';
2
+ /**
3
+ * Creates a command that focuses an element by selector and passes the result to a message constructor.
4
+ * Passes true if the element was found and focused, false otherwise.
5
+ * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to focus.
6
+ * This follows the same approach as Elm's Browser.Dom.focus.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * Task.focus('#email-input', success => InputFocused({ success }))
11
+ * ```
12
+ */
13
+ export declare const focus: <Message>(selector: string, f: (success: boolean) => Message) => Effect.Effect<Message>;
14
+ /**
15
+ * Creates a command that opens a dialog element as a modal using `showModal()`.
16
+ * Passes true if the element was found and opened, false otherwise.
17
+ * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to show.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * Task.showModal('#my-dialog', success => ModalOpened({ success }))
22
+ * ```
23
+ */
24
+ export declare const showModal: <Message>(selector: string, f: (success: boolean) => Message) => Effect.Effect<Message>;
25
+ /**
26
+ * Creates a command that closes a dialog element using `.close()`.
27
+ * Passes true if the element was found and closed, false otherwise.
28
+ * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to close.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * Task.closeModal('#my-dialog', success => ModalClosed({ success }))
33
+ * ```
34
+ */
35
+ export declare const closeModal: <Message>(selector: string, f: (success: boolean) => Message) => Effect.Effect<Message>;
36
+ /**
37
+ * Creates a command that programmatically clicks an element by selector and passes the result to a message constructor.
38
+ * Passes true if the element was found and clicked, false otherwise.
39
+ * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to click.
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * Task.clickElement('#menu-item-2', success => ItemClicked({ success }))
44
+ * ```
45
+ */
46
+ export declare const clickElement: <Message>(selector: string, f: (success: boolean) => Message) => Effect.Effect<Message>;
47
+ /**
48
+ * Creates a command that scrolls an element into view by selector and passes the result to a message constructor.
49
+ * Passes true if the element was found and scrolled, false otherwise.
50
+ * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to scroll.
51
+ * Uses `{ block: 'nearest' }` to avoid unnecessary scrolling when the element is already visible.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * Task.scrollIntoView('#active-item', success => ItemScrolled({ success }))
56
+ * ```
57
+ */
58
+ export declare const scrollIntoView: <Message>(selector: string, f: (success: boolean) => Message) => Effect.Effect<Message>;
59
+ //# sourceMappingURL=dom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/task/dom.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B;;;;;;;;;;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,YAAY,GAAI,OAAO,EAClC,UAAU,MAAM,EAChB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,KAC/B,MAAM,CAAC,MAAM,CAAC,OAAO,CAWpB,CAAA;AAEJ;;;;;;;;;;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"}
@@ -0,0 +1,113 @@
1
+ import { Effect } from 'effect';
2
+ /**
3
+ * Creates a command that focuses an element by selector and passes the result to a message constructor.
4
+ * Passes true if the element was found and focused, false otherwise.
5
+ * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to focus.
6
+ * This follows the same approach as Elm's Browser.Dom.focus.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * Task.focus('#email-input', success => InputFocused({ success }))
11
+ * ```
12
+ */
13
+ export const focus = (selector, f) => Effect.async(resume => {
14
+ requestAnimationFrame(() => {
15
+ const element = document.querySelector(selector);
16
+ if (element instanceof HTMLElement) {
17
+ element.focus();
18
+ resume(Effect.succeed(f(true)));
19
+ }
20
+ else {
21
+ resume(Effect.succeed(f(false)));
22
+ }
23
+ });
24
+ });
25
+ /**
26
+ * Creates a command that opens a dialog element as a modal using `showModal()`.
27
+ * Passes true if the element was found and opened, false otherwise.
28
+ * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to show.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * Task.showModal('#my-dialog', success => ModalOpened({ success }))
33
+ * ```
34
+ */
35
+ export const showModal = (selector, f) => Effect.async(resume => {
36
+ requestAnimationFrame(() => {
37
+ const element = document.querySelector(selector);
38
+ if (element instanceof HTMLDialogElement) {
39
+ element.showModal();
40
+ resume(Effect.succeed(f(true)));
41
+ }
42
+ else {
43
+ resume(Effect.succeed(f(false)));
44
+ }
45
+ });
46
+ });
47
+ /**
48
+ * Creates a command that closes a dialog element using `.close()`.
49
+ * Passes true if the element was found and closed, false otherwise.
50
+ * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to close.
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * Task.closeModal('#my-dialog', success => ModalClosed({ success }))
55
+ * ```
56
+ */
57
+ export const closeModal = (selector, f) => Effect.async(resume => {
58
+ requestAnimationFrame(() => {
59
+ const element = document.querySelector(selector);
60
+ if (element instanceof HTMLDialogElement) {
61
+ element.close();
62
+ resume(Effect.succeed(f(true)));
63
+ }
64
+ else {
65
+ resume(Effect.succeed(f(false)));
66
+ }
67
+ });
68
+ });
69
+ /**
70
+ * Creates a command that programmatically clicks an element by selector and passes the result to a message constructor.
71
+ * Passes true if the element was found and clicked, false otherwise.
72
+ * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to click.
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * Task.clickElement('#menu-item-2', success => ItemClicked({ success }))
77
+ * ```
78
+ */
79
+ export const clickElement = (selector, f) => Effect.async(resume => {
80
+ requestAnimationFrame(() => {
81
+ const element = document.querySelector(selector);
82
+ if (element instanceof HTMLElement) {
83
+ element.click();
84
+ resume(Effect.succeed(f(true)));
85
+ }
86
+ else {
87
+ resume(Effect.succeed(f(false)));
88
+ }
89
+ });
90
+ });
91
+ /**
92
+ * Creates a command that scrolls an element into view by selector and passes the result to a message constructor.
93
+ * Passes true if the element was found and scrolled, false otherwise.
94
+ * Uses requestAnimationFrame to ensure the DOM tree is updated and nodes exist before attempting to scroll.
95
+ * Uses `{ block: 'nearest' }` to avoid unnecessary scrolling when the element is already visible.
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * Task.scrollIntoView('#active-item', success => ItemScrolled({ success }))
100
+ * ```
101
+ */
102
+ export const scrollIntoView = (selector, f) => Effect.async(resume => {
103
+ requestAnimationFrame(() => {
104
+ const element = document.querySelector(selector);
105
+ if (element instanceof HTMLElement) {
106
+ element.scrollIntoView({ block: 'nearest' });
107
+ resume(Effect.succeed(f(true)));
108
+ }
109
+ else {
110
+ resume(Effect.succeed(f(false)));
111
+ }
112
+ });
113
+ });
@@ -1,109 +1,7 @@
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 { getTime, getTimeZone, getZonedTime, getZonedTimeIn } from './time';
2
+ export { focus, showModal, closeModal, clickElement, scrollIntoView, } from './dom';
3
+ export { delay, nextFrame, waitForTransitions } from './timing';
4
+ export { randomInt } from './random';
5
+ export { lockScroll, unlockScroll } from './scrollLock';
6
+ export { inertOthers, restoreInert } from './inert';
109
7
  //# 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,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,6 @@
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 { getTime, getTimeZone, getZonedTime, getZonedTimeIn } from './time';
2
+ export { focus, showModal, closeModal, clickElement, scrollIntoView, } from './dom';
3
+ export { delay, nextFrame, waitForTransitions } from './timing';
4
+ export { randomInt } from './random';
5
+ export { lockScroll, unlockScroll } from './scrollLock';
6
+ export { inertOthers, restoreInert } from './inert';
@@ -0,0 +1,25 @@
1
+ import { Effect } from 'effect';
2
+ /**
3
+ * Creates a command that marks all DOM elements outside the given selectors as
4
+ * `inert` and `aria-hidden="true"`. Walks each allowed element up to
5
+ * `document.body`, marking siblings that don't contain an allowed element.
6
+ * Uses reference counting so nested calls are safe.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * Task.inertOthers('my-menu', ['#menu-button', '#menu-items'], () => NoOp())
11
+ * ```
12
+ */
13
+ export declare const inertOthers: <Message>(id: string, allowedSelectors: ReadonlyArray<string>, f: () => Message) => Effect.Effect<Message>;
14
+ /**
15
+ * Creates a command that restores all elements previously marked inert by
16
+ * `inertOthers` for the given ID. Safe to call without a preceding
17
+ * `inertOthers` — acts as a no-op in that case.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * Task.restoreInert('my-menu', () => NoOp())
22
+ * ```
23
+ */
24
+ export declare const restoreInert: <Message>(id: string, f: () => Message) => Effect.Effect<Message>;
25
+ //# 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;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,EACjC,IAAI,MAAM,EACV,kBAAkB,aAAa,CAAC,MAAM,CAAC,EACvC,GAAG,MAAM,OAAO,KACf,MAAM,CAAC,MAAM,CAAC,OAAO,CAepB,CAAA;AAEJ;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,EAClC,IAAI,MAAM,EACV,GAAG,MAAM,OAAO,KACf,MAAM,CAAC,MAAM,CAAC,OAAO,CAUpB,CAAA"}