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.
- package/README.md +11 -16
- package/dist/effectExtensions/optionExtensions.d.ts +4 -0
- package/dist/effectExtensions/optionExtensions.d.ts.map +1 -1
- package/dist/effectExtensions/optionExtensions.js +2 -1
- package/dist/fieldValidation/index.d.ts +8 -8
- package/dist/fieldValidation/index.d.ts.map +1 -1
- package/dist/fieldValidation/index.js +9 -8
- package/dist/html/index.d.ts +57 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +69 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/message/index.d.ts +2 -0
- package/dist/message/index.d.ts.map +1 -0
- package/dist/message/index.js +1 -0
- package/dist/message/public.d.ts +2 -0
- package/dist/message/public.d.ts.map +1 -0
- package/dist/message/public.js +1 -0
- package/dist/route/index.d.ts +1 -0
- package/dist/route/index.d.ts.map +1 -1
- package/dist/route/index.js +1 -0
- package/dist/route/parser.js +17 -17
- package/dist/route/public.d.ts +1 -1
- package/dist/route/public.d.ts.map +1 -1
- package/dist/route/public.js +1 -1
- package/dist/runtime/browserListeners.d.ts.map +1 -1
- package/dist/runtime/browserListeners.js +2 -2
- package/dist/runtime/runtime.d.ts +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +8 -8
- package/dist/runtime/urlRequest.d.ts +4 -4
- package/dist/runtime/urlRequest.d.ts.map +1 -1
- package/dist/runtime/urlRequest.js +3 -2
- package/dist/schema/index.d.ts +44 -10
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +19 -15
- package/dist/schema/public.d.ts +1 -0
- package/dist/schema/public.d.ts.map +1 -1
- package/dist/task/dom.d.ts +59 -0
- package/dist/task/dom.d.ts.map +1 -0
- package/dist/task/dom.js +113 -0
- package/dist/task/index.d.ts +6 -85
- package/dist/task/index.d.ts.map +1 -1
- package/dist/task/index.js +6 -131
- package/dist/task/inert.d.ts +25 -0
- package/dist/task/inert.d.ts.map +1 -0
- package/dist/task/inert.js +88 -0
- package/dist/task/public.d.ts +1 -1
- package/dist/task/public.d.ts.map +1 -1
- package/dist/task/public.js +1 -1
- package/dist/task/random.d.ts +12 -0
- package/dist/task/random.d.ts.map +1 -0
- package/dist/task/random.js +11 -0
- package/dist/task/scrollLock.d.ts +24 -0
- package/dist/task/scrollLock.d.ts.map +1 -0
- package/dist/task/scrollLock.js +48 -0
- package/dist/task/time.d.ts +42 -0
- package/dist/task/time.d.ts.map +1 -0
- package/dist/task/time.js +54 -0
- package/dist/task/timing.d.ts +36 -0
- package/dist/task/timing.d.ts.map +1 -0
- package/dist/task/timing.js +55 -0
- package/dist/ui/dialog/index.d.ts +4 -4
- package/dist/ui/dialog/index.d.ts.map +1 -1
- package/dist/ui/dialog/index.js +8 -8
- package/dist/ui/disclosure/index.d.ts +4 -4
- package/dist/ui/disclosure/index.d.ts.map +1 -1
- package/dist/ui/disclosure/index.js +8 -8
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/keyboard.d.ts +4 -0
- package/dist/ui/keyboard.d.ts.map +1 -0
- package/dist/ui/keyboard.js +7 -0
- package/dist/ui/menu/index.d.ts +189 -0
- package/dist/ui/menu/index.d.ts.map +1 -0
- package/dist/ui/menu/index.js +502 -0
- package/dist/ui/menu/public.d.ts +3 -0
- package/dist/ui/menu/public.d.ts.map +1 -0
- package/dist/ui/menu/public.js +1 -0
- package/dist/ui/tabs/index.d.ts +8 -10
- package/dist/ui/tabs/index.d.ts.map +1 -1
- package/dist/ui/tabs/index.js +17 -28
- package/dist/url/index.d.ts +1 -1
- package/dist/url/index.js +4 -4
- package/package.json +14 -1
package/dist/ui/tabs/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { Array, Match as M, Option,
|
|
1
|
+
import { Array, Match as M, Option, Schema as S, String, pipe } from 'effect';
|
|
2
2
|
import { html } from '../../html';
|
|
3
|
-
import {
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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,
|
|
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(
|
|
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
|
-
|
|
86
|
-
M.value(
|
|
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
|
|
110
|
-
|
|
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:
|
|
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),
|
package/dist/url/index.d.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
55
|
+
encode: url => {
|
|
56
56
|
const search = Option.match(url.search, {
|
|
57
57
|
onNone: () => '',
|
|
58
|
-
onSome:
|
|
58
|
+
onSome: s => `?${s}`,
|
|
59
59
|
});
|
|
60
60
|
const hash = Option.match(url.hash, {
|
|
61
61
|
onNone: () => '',
|
|
62
|
-
onSome:
|
|
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.
|
|
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"
|