foldkit 0.17.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 (87) hide show
  1. package/README.md +11 -16
  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 +8 -8
  6. package/dist/fieldValidation/index.d.ts.map +1 -1
  7. package/dist/fieldValidation/index.js +9 -8
  8. package/dist/html/index.d.ts +57 -1
  9. package/dist/html/index.d.ts.map +1 -1
  10. package/dist/html/index.js +69 -7
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -0
  14. package/dist/message/index.d.ts +2 -0
  15. package/dist/message/index.d.ts.map +1 -0
  16. package/dist/message/index.js +1 -0
  17. package/dist/message/public.d.ts +2 -0
  18. package/dist/message/public.d.ts.map +1 -0
  19. package/dist/message/public.js +1 -0
  20. package/dist/route/index.d.ts +1 -0
  21. package/dist/route/index.d.ts.map +1 -1
  22. package/dist/route/index.js +1 -0
  23. package/dist/route/parser.js +17 -17
  24. package/dist/route/public.d.ts +1 -1
  25. package/dist/route/public.d.ts.map +1 -1
  26. package/dist/route/public.js +1 -1
  27. package/dist/runtime/browserListeners.d.ts.map +1 -1
  28. package/dist/runtime/browserListeners.js +2 -2
  29. package/dist/runtime/runtime.d.ts +1 -1
  30. package/dist/runtime/runtime.d.ts.map +1 -1
  31. package/dist/runtime/runtime.js +8 -8
  32. package/dist/runtime/urlRequest.d.ts +4 -4
  33. package/dist/runtime/urlRequest.d.ts.map +1 -1
  34. package/dist/runtime/urlRequest.js +3 -2
  35. package/dist/schema/index.d.ts +44 -10
  36. package/dist/schema/index.d.ts.map +1 -1
  37. package/dist/schema/index.js +19 -15
  38. package/dist/schema/public.d.ts +1 -0
  39. package/dist/schema/public.d.ts.map +1 -1
  40. package/dist/task/dom.d.ts +59 -0
  41. package/dist/task/dom.d.ts.map +1 -0
  42. package/dist/task/dom.js +113 -0
  43. package/dist/task/index.d.ts +6 -85
  44. package/dist/task/index.d.ts.map +1 -1
  45. package/dist/task/index.js +6 -131
  46. package/dist/task/inert.d.ts +25 -0
  47. package/dist/task/inert.d.ts.map +1 -0
  48. package/dist/task/inert.js +88 -0
  49. package/dist/task/public.d.ts +1 -1
  50. package/dist/task/public.d.ts.map +1 -1
  51. package/dist/task/public.js +1 -1
  52. package/dist/task/random.d.ts +12 -0
  53. package/dist/task/random.d.ts.map +1 -0
  54. package/dist/task/random.js +11 -0
  55. package/dist/task/scrollLock.d.ts +24 -0
  56. package/dist/task/scrollLock.d.ts.map +1 -0
  57. package/dist/task/scrollLock.js +48 -0
  58. package/dist/task/time.d.ts +42 -0
  59. package/dist/task/time.d.ts.map +1 -0
  60. package/dist/task/time.js +54 -0
  61. package/dist/task/timing.d.ts +36 -0
  62. package/dist/task/timing.d.ts.map +1 -0
  63. package/dist/task/timing.js +55 -0
  64. package/dist/ui/dialog/index.d.ts +4 -4
  65. package/dist/ui/dialog/index.d.ts.map +1 -1
  66. package/dist/ui/dialog/index.js +8 -8
  67. package/dist/ui/disclosure/index.d.ts +4 -4
  68. package/dist/ui/disclosure/index.d.ts.map +1 -1
  69. package/dist/ui/disclosure/index.js +8 -8
  70. package/dist/ui/index.d.ts +1 -0
  71. package/dist/ui/index.d.ts.map +1 -1
  72. package/dist/ui/index.js +1 -0
  73. package/dist/ui/keyboard.d.ts +4 -0
  74. package/dist/ui/keyboard.d.ts.map +1 -0
  75. package/dist/ui/keyboard.js +7 -0
  76. package/dist/ui/menu/index.d.ts +189 -0
  77. package/dist/ui/menu/index.d.ts.map +1 -0
  78. package/dist/ui/menu/index.js +502 -0
  79. package/dist/ui/menu/public.d.ts +3 -0
  80. package/dist/ui/menu/public.d.ts.map +1 -0
  81. package/dist/ui/menu/public.js +1 -0
  82. package/dist/ui/tabs/index.d.ts +8 -10
  83. package/dist/ui/tabs/index.d.ts.map +1 -1
  84. package/dist/ui/tabs/index.js +17 -28
  85. package/dist/url/index.d.ts +1 -1
  86. package/dist/url/index.js +4 -4
  87. package/package.json +14 -1
@@ -1,8 +1,10 @@
1
- import { Array, Match as M, Option, Predicate, Schema as S, String, pipe, } from 'effect';
1
+ import { Array, Match as M, Option, Schema as S, String, pipe } from 'effect';
2
2
  import { html } from '../../html';
3
- import { ts } from '../../schema';
3
+ import { m } from '../../message';
4
4
  import { evo } from '../../struct';
5
5
  import * as Task from '../../task';
6
+ import { keyToIndex } from '../keyboard';
7
+ export { wrapIndex, findFirstEnabledIndex, keyToIndex } from '../keyboard';
6
8
  // MODEL
7
9
  /** Controls the tab list layout direction and which arrow keys navigate between tabs. */
8
10
  export const Orientation = S.Literal('Horizontal', 'Vertical');
@@ -18,11 +20,11 @@ export const Model = S.Struct({
18
20
  });
19
21
  // MESSAGE
20
22
  /** Sent when a tab is selected via click or keyboard. Updates both the active and focused indices. */
21
- export const TabSelected = ts('TabSelected', { index: S.Number });
23
+ export const TabSelected = m('TabSelected', { index: S.Number });
22
24
  /** Sent when a tab receives keyboard focus in `Manual` mode without being activated. */
23
- export const TabFocused = ts('TabFocused', { index: S.Number });
25
+ export const TabFocused = m('TabFocused', { index: S.Number });
24
26
  /** Placeholder message used when no action is needed, such as after a focus command completes. */
25
- export const NoOp = ts('NoOp');
27
+ export const NoOp = m('NoOp');
26
28
  /** Union of all messages the tabs component can produce. */
27
29
  export const Message = S.Union(TabSelected, TabFocused, NoOp);
28
30
  /** Creates an initial tabs model from a config. Defaults to first tab, horizontal orientation, and automatic activation. */
@@ -46,33 +48,26 @@ export const update = (model, message) => M.value(message).pipe(M.withReturnType
46
48
  activeIndex: () => index,
47
49
  focusedIndex: () => index,
48
50
  }),
49
- [Task.focus(tabSelector, () => NoOp.make())],
51
+ [Task.focus(tabSelector, () => NoOp())],
50
52
  ];
51
53
  },
52
54
  TabFocused: ({ index }) => {
53
55
  const tabSelector = `#${tabId(model.id, index)}`;
54
56
  return [
55
57
  evo(model, { focusedIndex: () => index }),
56
- [Task.focus(tabSelector, () => NoOp.make())],
58
+ [Task.focus(tabSelector, () => NoOp())],
57
59
  ];
58
60
  },
59
61
  NoOp: () => [model, []],
60
62
  }));
61
- // KEYBOARD
62
- export const wrapIndex = (index, length) => ((index % length) + length) % length;
63
- export const findFirstEnabledIndex = (tabCount, focusedIndex, isDisabled) => (startIndex, direction) => pipe(tabCount, Array.makeBy((step) => wrapIndex(startIndex + step * direction, tabCount)), Array.findFirst(Predicate.not(isDisabled)), Option.getOrElse(() => focusedIndex));
64
- export const keyToIndex = (nextKey, previousKey, tabCount, focusedIndex, isDisabled) => {
65
- const find = findFirstEnabledIndex(tabCount, focusedIndex, isDisabled);
66
- return (key) => M.value(key).pipe(M.when(nextKey, () => find(focusedIndex + 1, 1)), M.when(previousKey, () => find(focusedIndex - 1, -1)), M.whenOr('Home', 'PageUp', () => find(0, 1)), M.whenOr('End', 'PageDown', () => find(tabCount - 1, -1)), M.orElse(() => focusedIndex));
67
- };
68
63
  const tabPanelId = (id, index) => `${id}-panel-${index}`;
69
64
  const tabId = (id, index) => `${id}-tab-${index}`;
70
65
  /** Renders a headless tab group with accessible ARIA roles, roving tabindex, and keyboard navigation. */
71
66
  export const view = (config) => {
72
- const { div, empty, AriaControls, AriaDisabled, AriaLabelledBy, AriaOrientation, AriaSelected, Class, DataAttribute, Disabled, Hidden, Id, OnClick, OnKeyDown, Role, Tabindex, Type, keyed, } = html();
67
+ const { div, empty, AriaControls, AriaDisabled, AriaLabelledBy, AriaOrientation, AriaSelected, Class, DataAttribute, Disabled, Hidden, Id, OnClick, OnKeyDownPreventDefault, Role, Tabindex, Type, keyed, } = html();
73
68
  const { model, model: { id, orientation, activationMode, focusedIndex }, toMessage, tabs, tabToConfig, isTabDisabled, persistPanels, tabListElement = 'div', tabElement = 'button', panelElement = 'div', className, tabListClassName, } = config;
74
69
  const isDisabled = (index) => !!isTabDisabled &&
75
- pipe(tabs, Array.get(index), Option.exists((tab) => isTabDisabled(tab, index)));
70
+ pipe(tabs, Array.get(index), Option.exists(tab => isTabDisabled(tab, index)));
76
71
  const { nextKey, previousKey } = M.value(orientation).pipe(M.when('Horizontal', () => ({
77
72
  nextKey: 'ArrowRight',
78
73
  previousKey: 'ArrowLeft',
@@ -81,15 +76,9 @@ export const view = (config) => {
81
76
  previousKey: 'ArrowUp',
82
77
  })), M.exhaustive);
83
78
  const resolveKeyIndex = keyToIndex(nextKey, previousKey, tabs.length, focusedIndex, isDisabled);
84
- const handleAutomaticKeyDown = (key) =>
85
- /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
86
- M.value(key).pipe(M.whenOr(nextKey, previousKey, 'Home', 'End', 'PageUp', 'PageDown', () => toMessage(TabSelected.make({ index: resolveKeyIndex(key) }))), M.whenOr('Enter', ' ', () => toMessage(TabSelected.make({ index: focusedIndex }))), M.orElse(() => toMessage(NoOp.make())));
87
- const handleManualKeyDown = (key) =>
88
- /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
89
- M.value(key).pipe(M.whenOr(nextKey, previousKey, 'Home', 'End', 'PageUp', 'PageDown', () => toMessage(TabFocused.make({ index: resolveKeyIndex(key) }))), M.whenOr('Enter', ' ', () => toMessage(TabSelected.make({ index: focusedIndex }))), M.orElse(() => toMessage(NoOp.make())));
90
- const handleKeyDown = (key) =>
91
- /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
92
- M.value(activationMode).pipe(M.when('Automatic', () => handleAutomaticKeyDown(key)), M.when('Manual', () => handleManualKeyDown(key)), M.exhaustive);
79
+ const handleAutomaticKeyDown = (key) => M.value(key).pipe(M.whenOr(nextKey, previousKey, 'Home', 'End', 'PageUp', 'PageDown', () => Option.some(toMessage(TabSelected({ index: resolveKeyIndex(key) })))), M.whenOr('Enter', ' ', () => Option.some(toMessage(TabSelected({ index: focusedIndex })))), M.orElse(() => Option.none()));
80
+ const handleManualKeyDown = (key) => M.value(key).pipe(M.whenOr(nextKey, previousKey, 'Home', 'End', 'PageUp', 'PageDown', () => Option.some(toMessage(TabFocused({ index: resolveKeyIndex(key) })))), M.whenOr('Enter', ' ', () => Option.some(toMessage(TabSelected({ index: focusedIndex })))), M.orElse(() => Option.none()));
81
+ const handleKeyDown = (key) => M.value(activationMode).pipe(M.when('Automatic', () => handleAutomaticKeyDown(key)), M.when('Manual', () => handleManualKeyDown(key)), M.exhaustive);
93
82
  const tabButtons = Array.map(tabs, (tab, index) => {
94
83
  const isActive = index === model.activeIndex;
95
84
  const isFocused = index === focusedIndex;
@@ -106,8 +95,8 @@ export const view = (config) => {
106
95
  ...(isActive ? [DataAttribute('selected', '')] : []),
107
96
  ...(isTabDisabledAtIndex
108
97
  ? [Disabled(true), AriaDisabled(true), DataAttribute('disabled', '')]
109
- : [OnClick(toMessage(TabSelected.make({ index })))]),
110
- OnKeyDown(handleKeyDown),
98
+ : [OnClick(toMessage(TabSelected({ index })))]),
99
+ OnKeyDownPreventDefault(handleKeyDown),
111
100
  ], [tabConfig.buttonContent]);
112
101
  });
113
102
  const allPanels = Array.map(tabs, (tab, index) => {
@@ -125,7 +114,7 @@ export const view = (config) => {
125
114
  });
126
115
  const activePanelOnly = pipe(tabs, Array.get(model.activeIndex), Option.match({
127
116
  onNone: () => empty,
128
- onSome: (tab) => {
117
+ onSome: tab => {
129
118
  const activeConfig = tabToConfig(tab, { isActive: true });
130
119
  return keyed(panelElement)(tabPanelId(id, model.activeIndex), [
131
120
  Class(activeConfig.panelClassName),
@@ -11,11 +11,11 @@ export declare const Url: S.Struct<{
11
11
  export type Url = typeof Url.Type;
12
12
  /** Parses a URL string into a `Url`, returning `Option.None` if invalid. */
13
13
  export declare const fromString: (str: string) => Option.Option<{
14
+ readonly search: Option.Option<string>;
14
15
  readonly protocol: string;
15
16
  readonly host: string;
16
17
  readonly port: Option.Option<string>;
17
18
  readonly pathname: string;
18
- readonly search: Option.Option<string>;
19
19
  readonly hash: Option.Option<string>;
20
20
  }>;
21
21
  /** Serializes a `Url` back to a string. */
package/dist/url/index.js CHANGED
@@ -31,7 +31,7 @@ const LocationAndHrefFromString = S.transformOrFail(S.String, LocationAndHref, {
31
31
  },
32
32
  };
33
33
  },
34
- catch: (error) => new ParseResult.Type(ast, urlString, `Invalid URL: ${error}`),
34
+ catch: error => new ParseResult.Type(ast, urlString, `Invalid URL: ${error}`),
35
35
  }),
36
36
  encode: ({ href, location }) => {
37
37
  const portString = location.port ? `:${location.port}` : '';
@@ -52,14 +52,14 @@ const UrlFromLocationAndHref = S.transform(LocationAndHref, Url, {
52
52
  hash: OptionExt.fromString(hashPart || ''),
53
53
  };
54
54
  },
55
- encode: (url) => {
55
+ encode: url => {
56
56
  const search = Option.match(url.search, {
57
57
  onNone: () => '',
58
- onSome: (s) => `?${s}`,
58
+ onSome: s => `?${s}`,
59
59
  });
60
60
  const hash = Option.match(url.hash, {
61
61
  onNone: () => '',
62
- onSome: (h) => `#${h}`,
62
+ onSome: h => `#${h}`,
63
63
  });
64
64
  const href = `${url.pathname}${search}${hash}`;
65
65
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foldkit",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "description": "Elm-inspired UI framework powered by Effect",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,6 +15,10 @@
15
15
  "types": "./dist/html/public.d.ts",
16
16
  "import": "./dist/html/public.js"
17
17
  },
18
+ "./message": {
19
+ "types": "./dist/message/public.d.ts",
20
+ "import": "./dist/message/public.js"
21
+ },
18
22
  "./navigation": {
19
23
  "types": "./dist/navigation/public.d.ts",
20
24
  "import": "./dist/navigation/public.js"
@@ -43,10 +47,18 @@
43
47
  "types": "./dist/ui/index.d.ts",
44
48
  "import": "./dist/ui/index.js"
45
49
  },
50
+ "./ui/dialog": {
51
+ "types": "./dist/ui/dialog/public.d.ts",
52
+ "import": "./dist/ui/dialog/public.js"
53
+ },
46
54
  "./ui/disclosure": {
47
55
  "types": "./dist/ui/disclosure/public.d.ts",
48
56
  "import": "./dist/ui/disclosure/public.js"
49
57
  },
58
+ "./ui/menu": {
59
+ "types": "./dist/ui/menu/public.d.ts",
60
+ "import": "./dist/ui/menu/public.js"
61
+ },
50
62
  "./ui/tabs": {
51
63
  "types": "./dist/ui/tabs/public.d.ts",
52
64
  "import": "./dist/ui/tabs/public.js"
@@ -98,6 +110,7 @@
98
110
  "clean": "rimraf dist *.tsbuildinfo",
99
111
  "build": "pnpm run clean && tsc -b",
100
112
  "watch": "tsc -b --watch",
113
+ "lint": "eslint src --ignore-pattern '**/*.test.ts'",
101
114
  "typecheck": "tsc -b --noEmit",
102
115
  "test": "vitest run",
103
116
  "docs": "typedoc"