@foldkit/ui 0.112.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/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/anchor.d.ts +38 -0
- package/dist/anchor.d.ts.map +1 -0
- package/dist/anchor.js +142 -0
- package/dist/animation/index.d.ts +49 -0
- package/dist/animation/index.d.ts.map +1 -0
- package/dist/animation/index.js +75 -0
- package/dist/animation/public.d.ts +3 -0
- package/dist/animation/public.d.ts.map +1 -0
- package/dist/animation/public.js +1 -0
- package/dist/animation/schema.d.ts +43 -0
- package/dist/animation/schema.d.ts.map +1 -0
- package/dist/animation/schema.js +41 -0
- package/dist/animation/update.d.ts +24 -0
- package/dist/animation/update.d.ts.map +1 -0
- package/dist/animation/update.js +67 -0
- package/dist/button/index.d.ts +17 -0
- package/dist/button/index.d.ts.map +1 -0
- package/dist/button/index.js +22 -0
- package/dist/button/public.d.ts +3 -0
- package/dist/button/public.d.ts.map +1 -0
- package/dist/button/public.js +1 -0
- package/dist/calendar/index.d.ts +462 -0
- package/dist/calendar/index.d.ts.map +1 -0
- package/dist/calendar/index.js +825 -0
- package/dist/calendar/public.d.ts +3 -0
- package/dist/calendar/public.d.ts.map +1 -0
- package/dist/calendar/public.js +1 -0
- package/dist/checkbox/index.d.ts +119 -0
- package/dist/checkbox/index.d.ts.map +1 -0
- package/dist/checkbox/index.js +111 -0
- package/dist/checkbox/public.d.ts +3 -0
- package/dist/checkbox/public.d.ts.map +1 -0
- package/dist/checkbox/public.js +1 -0
- package/dist/combobox/multi.d.ts +183 -0
- package/dist/combobox/multi.d.ts.map +1 -0
- package/dist/combobox/multi.js +81 -0
- package/dist/combobox/multiPublic.d.ts +3 -0
- package/dist/combobox/multiPublic.d.ts.map +1 -0
- package/dist/combobox/multiPublic.js +1 -0
- package/dist/combobox/public.d.ts +7 -0
- package/dist/combobox/public.d.ts.map +1 -0
- package/dist/combobox/public.js +3 -0
- package/dist/combobox/shared.d.ts +423 -0
- package/dist/combobox/shared.d.ts.map +1 -0
- package/dist/combobox/shared.js +708 -0
- package/dist/combobox/single.d.ts +198 -0
- package/dist/combobox/single.d.ts.map +1 -0
- package/dist/combobox/single.js +106 -0
- package/dist/datePicker/index.d.ts +457 -0
- package/dist/datePicker/index.d.ts.map +1 -0
- package/dist/datePicker/index.js +318 -0
- package/dist/datePicker/public.d.ts +3 -0
- package/dist/datePicker/public.d.ts.map +1 -0
- package/dist/datePicker/public.js +1 -0
- package/dist/dialog/index.d.ts +160 -0
- package/dist/dialog/index.d.ts.map +1 -0
- package/dist/dialog/index.js +211 -0
- package/dist/dialog/public.d.ts +3 -0
- package/dist/dialog/public.d.ts.map +1 -0
- package/dist/dialog/public.js +1 -0
- package/dist/disclosure/index.d.ts +110 -0
- package/dist/disclosure/index.d.ts.map +1 -0
- package/dist/disclosure/index.js +111 -0
- package/dist/disclosure/public.d.ts +3 -0
- package/dist/disclosure/public.d.ts.map +1 -0
- package/dist/disclosure/public.js +1 -0
- package/dist/dragAndDrop/index.d.ts +540 -0
- package/dist/dragAndDrop/index.d.ts.map +1 -0
- package/dist/dragAndDrop/index.js +535 -0
- package/dist/dragAndDrop/public.d.ts +3 -0
- package/dist/dragAndDrop/public.d.ts.map +1 -0
- package/dist/dragAndDrop/public.js +1 -0
- package/dist/fieldset/index.d.ts +21 -0
- package/dist/fieldset/index.d.ts.map +1 -0
- package/dist/fieldset/index.js +25 -0
- package/dist/fieldset/public.d.ts +3 -0
- package/dist/fieldset/public.d.ts.map +1 -0
- package/dist/fieldset/public.js +1 -0
- package/dist/fileDrop/index.d.ts +109 -0
- package/dist/fileDrop/index.d.ts.map +1 -0
- package/dist/fileDrop/index.js +127 -0
- package/dist/fileDrop/public.d.ts +3 -0
- package/dist/fileDrop/public.d.ts.map +1 -0
- package/dist/fileDrop/public.js +1 -0
- package/dist/group.d.ts +8 -0
- package/dist/group.d.ts.map +1 -0
- package/dist/group.js +13 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/input/index.d.ts +26 -0
- package/dist/input/index.d.ts.map +1 -0
- package/dist/input/index.js +43 -0
- package/dist/input/public.d.ts +3 -0
- package/dist/input/public.d.ts.map +1 -0
- package/dist/input/public.js +1 -0
- package/dist/internal/optionExtensions.d.ts +6 -0
- package/dist/internal/optionExtensions.d.ts.map +1 -0
- package/dist/internal/optionExtensions.js +2 -0
- package/dist/keyboard.d.ts +6 -0
- package/dist/keyboard.d.ts.map +1 -0
- package/dist/keyboard.js +9 -0
- package/dist/listbox/multi.d.ts +189 -0
- package/dist/listbox/multi.d.ts.map +1 -0
- package/dist/listbox/multi.js +65 -0
- package/dist/listbox/multiPublic.d.ts +3 -0
- package/dist/listbox/multiPublic.d.ts.map +1 -0
- package/dist/listbox/multiPublic.js +1 -0
- package/dist/listbox/public.d.ts +7 -0
- package/dist/listbox/public.d.ts.map +1 -0
- package/dist/listbox/public.js +3 -0
- package/dist/listbox/shared.d.ts +432 -0
- package/dist/listbox/shared.d.ts.map +1 -0
- package/dist/listbox/shared.js +670 -0
- package/dist/listbox/single.d.ts +207 -0
- package/dist/listbox/single.d.ts.map +1 -0
- package/dist/listbox/single.js +73 -0
- package/dist/menu/index.d.ts +368 -0
- package/dist/menu/index.d.ts.map +1 -0
- package/dist/menu/index.js +682 -0
- package/dist/menu/public.d.ts +4 -0
- package/dist/menu/public.d.ts.map +1 -0
- package/dist/menu/public.js +1 -0
- package/dist/popover/index.d.ts +267 -0
- package/dist/popover/index.d.ts.map +1 -0
- package/dist/popover/index.js +346 -0
- package/dist/popover/public.d.ts +4 -0
- package/dist/popover/public.d.ts.map +1 -0
- package/dist/popover/public.js +1 -0
- package/dist/radioGroup/index.d.ts +169 -0
- package/dist/radioGroup/index.d.ts.map +1 -0
- package/dist/radioGroup/index.js +197 -0
- package/dist/radioGroup/public.d.ts +3 -0
- package/dist/radioGroup/public.d.ts.map +1 -0
- package/dist/radioGroup/public.js +1 -0
- package/dist/select/index.d.ts +24 -0
- package/dist/select/index.d.ts.map +1 -0
- package/dist/select/index.js +40 -0
- package/dist/select/public.d.ts +3 -0
- package/dist/select/public.d.ts.map +1 -0
- package/dist/select/public.js +1 -0
- package/dist/slider/index.d.ts +318 -0
- package/dist/slider/index.d.ts.map +1 -0
- package/dist/slider/index.js +337 -0
- package/dist/slider/public.d.ts +3 -0
- package/dist/slider/public.d.ts.map +1 -0
- package/dist/slider/public.js +1 -0
- package/dist/switch/index.d.ts +99 -0
- package/dist/switch/index.d.ts.map +1 -0
- package/dist/switch/index.js +107 -0
- package/dist/switch/public.d.ts +3 -0
- package/dist/switch/public.d.ts.map +1 -0
- package/dist/switch/public.js +1 -0
- package/dist/tabs/index.d.ts +155 -0
- package/dist/tabs/index.d.ts.map +1 -0
- package/dist/tabs/index.js +185 -0
- package/dist/tabs/public.d.ts +3 -0
- package/dist/tabs/public.d.ts.map +1 -0
- package/dist/tabs/public.js +1 -0
- package/dist/test/apps/disabledButton.d.ts +38 -0
- package/dist/test/apps/disabledButton.d.ts.map +1 -0
- package/dist/test/apps/disabledButton.js +71 -0
- package/dist/textarea/index.d.ts +26 -0
- package/dist/textarea/index.d.ts.map +1 -0
- package/dist/textarea/index.js +44 -0
- package/dist/textarea/public.d.ts +3 -0
- package/dist/textarea/public.d.ts.map +1 -0
- package/dist/textarea/public.js +1 -0
- package/dist/toast/index.d.ts +608 -0
- package/dist/toast/index.d.ts.map +1 -0
- package/dist/toast/index.js +146 -0
- package/dist/toast/public.d.ts +4 -0
- package/dist/toast/public.d.ts.map +1 -0
- package/dist/toast/public.js +1 -0
- package/dist/toast/schema.d.ts +154 -0
- package/dist/toast/schema.d.ts.map +1 -0
- package/dist/toast/schema.js +93 -0
- package/dist/toast/update.d.ts +510 -0
- package/dist/toast/update.d.ts.map +1 -0
- package/dist/toast/update.js +225 -0
- package/dist/tooltip/index.d.ts +170 -0
- package/dist/tooltip/index.d.ts.map +1 -0
- package/dist/tooltip/index.js +253 -0
- package/dist/tooltip/public.d.ts +4 -0
- package/dist/tooltip/public.d.ts.map +1 -0
- package/dist/tooltip/public.js +1 -0
- package/dist/typeahead.d.ts +4 -0
- package/dist/typeahead.d.ts.map +1 -0
- package/dist/typeahead.js +14 -0
- package/dist/virtualList/index.d.ts +203 -0
- package/dist/virtualList/index.d.ts.map +1 -0
- package/dist/virtualList/index.js +392 -0
- package/dist/virtualList/public.d.ts +3 -0
- package/dist/virtualList/public.d.ts.map +1 -0
- package/dist/virtualList/public.js +1 -0
- package/dist/vitest-setup.d.ts +2 -0
- package/dist/vitest-setup.d.ts.map +1 -0
- package/dist/vitest-setup.js +2 -0
- package/package.json +161 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Option, Schema as S } from 'effect';
|
|
2
|
+
import type { Command } from 'foldkit/command';
|
|
3
|
+
import { type ChildAttribute, type Html } from 'foldkit/html';
|
|
4
|
+
import { type Reflect } from 'foldkit/submodel';
|
|
5
|
+
/** Schema for the switch component's state, tracking the toggle's checked status. */
|
|
6
|
+
export declare const Model: S.Struct<{
|
|
7
|
+
readonly id: S.String;
|
|
8
|
+
readonly isChecked: S.Boolean;
|
|
9
|
+
}>;
|
|
10
|
+
export type Model = typeof Model.Type;
|
|
11
|
+
/** Sent when the user toggles the switch via click or Space key. */
|
|
12
|
+
export declare const Toggled: import("foldkit/schema").CallableTaggedStruct<"Toggled", {}>;
|
|
13
|
+
/** Sent to set the checked state to a specific value. Use this for
|
|
14
|
+
* programmatic state assignment (e.g. a "select all" handler that forces
|
|
15
|
+
* all child switches to the same state) where `Toggled`'s flip semantics
|
|
16
|
+
* would not reliably reach the desired state. */
|
|
17
|
+
export declare const SetChecked: import("foldkit/schema").CallableTaggedStruct<"SetChecked", {
|
|
18
|
+
isChecked: S.Boolean;
|
|
19
|
+
}>;
|
|
20
|
+
/** Schema for all messages the switch component can produce. */
|
|
21
|
+
export declare const Message: S.Union<readonly [import("foldkit/schema").CallableTaggedStruct<"Toggled", {}>, import("foldkit/schema").CallableTaggedStruct<"SetChecked", {
|
|
22
|
+
isChecked: S.Boolean;
|
|
23
|
+
}>]>;
|
|
24
|
+
export type Toggled = typeof Toggled.Type;
|
|
25
|
+
export type SetChecked = typeof SetChecked.Type;
|
|
26
|
+
export type Message = typeof Message.Type;
|
|
27
|
+
/** Sent to the parent each time the switch toggles. Carries the new
|
|
28
|
+
* checked state. Consumers pattern-match this in their `GotSwitchMessage`
|
|
29
|
+
* handler to lift the toggle into a domain Message (e.g., persisting the
|
|
30
|
+
* setting, dispatching a sync command). */
|
|
31
|
+
export declare const ToggledChecked: import("foldkit/schema").CallableTaggedStruct<"ToggledChecked", {
|
|
32
|
+
isChecked: S.Boolean;
|
|
33
|
+
}>;
|
|
34
|
+
/** Union of out-messages the switch component can produce. Surfaced as
|
|
35
|
+
* the third element of `update`'s return tuple and pattern-matched by
|
|
36
|
+
* the parent. */
|
|
37
|
+
export declare const OutMessage: S.Union<readonly [import("foldkit/schema").CallableTaggedStruct<"ToggledChecked", {
|
|
38
|
+
isChecked: S.Boolean;
|
|
39
|
+
}>]>;
|
|
40
|
+
export type ToggledChecked = typeof ToggledChecked.Type;
|
|
41
|
+
export type OutMessage = typeof OutMessage.Type;
|
|
42
|
+
/** Configuration for creating a switch model with `init`. */
|
|
43
|
+
export type InitConfig = Readonly<{
|
|
44
|
+
id: string;
|
|
45
|
+
isChecked?: boolean;
|
|
46
|
+
}>;
|
|
47
|
+
/** Creates an initial switch model from a config. Defaults to unchecked. */
|
|
48
|
+
export declare const init: (config: InitConfig) => Model;
|
|
49
|
+
/** Processes a switch message and returns the next model, commands, and
|
|
50
|
+
* a `ToggledChecked` OutMessage carrying the new checked state. */
|
|
51
|
+
export declare const update: (model: Model, message: Message) => readonly [Model, ReadonlyArray<Command<Message>>, Option.Option<OutMessage>];
|
|
52
|
+
/** Programmatically sets the checked state. Emits a `ToggledChecked`
|
|
53
|
+
* OutMessage just like a user-initiated toggle. Use this in domain-event
|
|
54
|
+
* handlers where you need to force a specific state. */
|
|
55
|
+
export declare const setChecked: (model: Model, isChecked: boolean) => readonly [Model, ReadonlyArray<Command<Message>>, Option.Option<OutMessage>];
|
|
56
|
+
/** Reflects an externally-sourced checked state onto the model without
|
|
57
|
+
* emitting an OutMessage. Use this to mirror external truth (saved
|
|
58
|
+
* settings, a server value) onto the switch without triggering the
|
|
59
|
+
* downstream reaction a user toggle would cause. Contrast with
|
|
60
|
+
* `setChecked`, which emits `ToggledChecked` so the parent reacts to a
|
|
61
|
+
* programmatic assignment the same way it reacts to a user toggle. Returns
|
|
62
|
+
* the model directly because it produces no commands and no OutMessage. */
|
|
63
|
+
export declare const reflectChecked: Reflect<Model, boolean>;
|
|
64
|
+
/** Attribute groups the switch component provides to the consumer's
|
|
65
|
+
* `toView` callback. Each group is a `ReadonlyArray<ChildAttribute>`
|
|
66
|
+
* whose event handlers dispatch through the Switch's boundary at
|
|
67
|
+
* event-fire time. See {@link Checkbox.CheckboxAttributes} for the full
|
|
68
|
+
* routing model. */
|
|
69
|
+
export type SwitchAttributes = Readonly<{
|
|
70
|
+
button: ReadonlyArray<ChildAttribute>;
|
|
71
|
+
label: ReadonlyArray<ChildAttribute>;
|
|
72
|
+
description: ReadonlyArray<ChildAttribute>;
|
|
73
|
+
hiddenInput: ReadonlyArray<ChildAttribute>;
|
|
74
|
+
}>;
|
|
75
|
+
/** Per-render view inputs passed to `view` via `h.submodel`'s `viewInputs` field. */
|
|
76
|
+
export type ViewInputs = Readonly<{
|
|
77
|
+
toView: (attributes: SwitchAttributes) => Html;
|
|
78
|
+
isDisabled?: boolean;
|
|
79
|
+
name?: string;
|
|
80
|
+
value?: string;
|
|
81
|
+
}>;
|
|
82
|
+
/** Renders an accessible switch toggle by building ARIA attribute groups
|
|
83
|
+
* and delegating layout to the consumer's `toView` callback. Designed
|
|
84
|
+
* to be embedded via `h.submodel`. */
|
|
85
|
+
export declare const view: import("foldkit/submodel").View<{
|
|
86
|
+
readonly id: string;
|
|
87
|
+
readonly isChecked: boolean;
|
|
88
|
+
}, {
|
|
89
|
+
readonly _tag: "Toggled";
|
|
90
|
+
} | {
|
|
91
|
+
readonly _tag: "SetChecked";
|
|
92
|
+
readonly isChecked: boolean;
|
|
93
|
+
}, Readonly<{
|
|
94
|
+
toView: (attributes: SwitchAttributes) => Html;
|
|
95
|
+
isDisabled?: boolean;
|
|
96
|
+
name?: string;
|
|
97
|
+
value?: string;
|
|
98
|
+
}>>;
|
|
99
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/switch/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,MAAM,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AAClE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,IAAI,EAGV,MAAM,cAAc,CAAA;AAGrB,OAAO,EAAE,KAAK,OAAO,EAAc,MAAM,kBAAkB,CAAA;AAI3D,qFAAqF;AACrF,eAAO,MAAM,KAAK;;;EAGhB,CAAA;AAEF,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAA;AAIrC,oEAAoE;AACpE,eAAO,MAAM,OAAO,8DAAe,CAAA;AAEnC;;;kDAGkD;AAClD,eAAO,MAAM,UAAU;;EAA4C,CAAA;AAEnE,gEAAgE;AAChE,eAAO,MAAM,OAAO;;IAAiC,CAAA;AAErD,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAEzC,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAA;AAE/C,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAIzC;;;4CAG4C;AAC5C,eAAO,MAAM,cAAc;;EAAgD,CAAA;AAE3E;;kBAEkB;AAClB,eAAO,MAAM,UAAU;;IAA4B,CAAA;AAEnD,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAA;AACvD,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAA;AAI/C,6DAA6D;AAC7D,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAC,CAAA;AAEF,4EAA4E;AAC5E,eAAO,MAAM,IAAI,GAAI,QAAQ,UAAU,KAAG,KAGxC,CAAA;AAIF;oEACoE;AACpE,eAAO,MAAM,MAAM,GACjB,OAAO,KAAK,EACZ,SAAS,OAAO,KACf,SAAS,CACV,KAAK,EACL,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAC/B,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAyBxB,CAAA;AAEH;;yDAEyD;AACzD,eAAO,MAAM,UAAU,GACrB,OAAO,KAAK,EACZ,WAAW,OAAO,KACjB,SAAS,CACV,KAAK,EACL,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAC/B,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CACkB,CAAA;AAE7C;;;;;;4EAM4E;AAC5E,eAAO,MAAM,cAAc,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAIlD,CAAA;AAID;;;;qBAIqB;AACrB,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,MAAM,EAAE,aAAa,CAAC,cAAc,CAAC,CAAA;IACrC,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC,CAAA;IACpC,WAAW,EAAE,aAAa,CAAC,cAAc,CAAC,CAAA;IAC1C,WAAW,EAAE,aAAa,CAAC,cAAc,CAAC,CAAA;CAC3C,CAAC,CAAA;AAEF,qFAAqF;AACrF,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC,MAAM,EAAE,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,CAAA;IAC9C,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAC,CAAA;AAKF;;uCAEuC;AACvC,eAAO,MAAM,IAAI;;;;;;;;;YAZP,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI;iBACjC,OAAO;WACb,MAAM;YACL,MAAM;GA2Df,CAAA"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Function, Match as M, Option, Schema as S } from 'effect';
|
|
2
|
+
import { childAttributes, html, } from 'foldkit/html';
|
|
3
|
+
import { m } from 'foldkit/message';
|
|
4
|
+
import { evo } from 'foldkit/struct';
|
|
5
|
+
import { defineView } from 'foldkit/submodel';
|
|
6
|
+
// MODEL
|
|
7
|
+
/** Schema for the switch component's state, tracking the toggle's checked status. */
|
|
8
|
+
export const Model = S.Struct({
|
|
9
|
+
id: S.String,
|
|
10
|
+
isChecked: S.Boolean,
|
|
11
|
+
});
|
|
12
|
+
// MESSAGE
|
|
13
|
+
/** Sent when the user toggles the switch via click or Space key. */
|
|
14
|
+
export const Toggled = m('Toggled');
|
|
15
|
+
/** Sent to set the checked state to a specific value. Use this for
|
|
16
|
+
* programmatic state assignment (e.g. a "select all" handler that forces
|
|
17
|
+
* all child switches to the same state) where `Toggled`'s flip semantics
|
|
18
|
+
* would not reliably reach the desired state. */
|
|
19
|
+
export const SetChecked = m('SetChecked', { isChecked: S.Boolean });
|
|
20
|
+
/** Schema for all messages the switch component can produce. */
|
|
21
|
+
export const Message = S.Union([Toggled, SetChecked]);
|
|
22
|
+
// OUT MESSAGE
|
|
23
|
+
/** Sent to the parent each time the switch toggles. Carries the new
|
|
24
|
+
* checked state. Consumers pattern-match this in their `GotSwitchMessage`
|
|
25
|
+
* handler to lift the toggle into a domain Message (e.g., persisting the
|
|
26
|
+
* setting, dispatching a sync command). */
|
|
27
|
+
export const ToggledChecked = m('ToggledChecked', { isChecked: S.Boolean });
|
|
28
|
+
/** Union of out-messages the switch component can produce. Surfaced as
|
|
29
|
+
* the third element of `update`'s return tuple and pattern-matched by
|
|
30
|
+
* the parent. */
|
|
31
|
+
export const OutMessage = S.Union([ToggledChecked]);
|
|
32
|
+
/** Creates an initial switch model from a config. Defaults to unchecked. */
|
|
33
|
+
export const init = (config) => ({
|
|
34
|
+
id: config.id,
|
|
35
|
+
isChecked: config.isChecked ?? false,
|
|
36
|
+
});
|
|
37
|
+
// UPDATE
|
|
38
|
+
/** Processes a switch message and returns the next model, commands, and
|
|
39
|
+
* a `ToggledChecked` OutMessage carrying the new checked state. */
|
|
40
|
+
export const update = (model, message) => M.value(message).pipe(M.withReturnType(), M.tagsExhaustive({
|
|
41
|
+
Toggled: () => {
|
|
42
|
+
const nextIsChecked = !model.isChecked;
|
|
43
|
+
return [
|
|
44
|
+
evo(model, { isChecked: () => nextIsChecked }),
|
|
45
|
+
[],
|
|
46
|
+
Option.some(ToggledChecked({ isChecked: nextIsChecked })),
|
|
47
|
+
];
|
|
48
|
+
},
|
|
49
|
+
SetChecked: ({ isChecked }) => [
|
|
50
|
+
evo(model, { isChecked: () => isChecked }),
|
|
51
|
+
[],
|
|
52
|
+
Option.some(ToggledChecked({ isChecked })),
|
|
53
|
+
],
|
|
54
|
+
}));
|
|
55
|
+
/** Programmatically sets the checked state. Emits a `ToggledChecked`
|
|
56
|
+
* OutMessage just like a user-initiated toggle. Use this in domain-event
|
|
57
|
+
* handlers where you need to force a specific state. */
|
|
58
|
+
export const setChecked = (model, isChecked) => update(model, SetChecked({ isChecked }));
|
|
59
|
+
/** Reflects an externally-sourced checked state onto the model without
|
|
60
|
+
* emitting an OutMessage. Use this to mirror external truth (saved
|
|
61
|
+
* settings, a server value) onto the switch without triggering the
|
|
62
|
+
* downstream reaction a user toggle would cause. Contrast with
|
|
63
|
+
* `setChecked`, which emits `ToggledChecked` so the parent reacts to a
|
|
64
|
+
* programmatic assignment the same way it reacts to a user toggle. Returns
|
|
65
|
+
* the model directly because it produces no commands and no OutMessage. */
|
|
66
|
+
export const reflectChecked = Function.dual(2, (model, isChecked) => evo(model, { isChecked: () => isChecked }));
|
|
67
|
+
const labelId = (id) => `${id}-label`;
|
|
68
|
+
const descriptionId = (id) => `${id}-description`;
|
|
69
|
+
/** Renders an accessible switch toggle by building ARIA attribute groups
|
|
70
|
+
* and delegating layout to the consumer's `toView` callback. Designed
|
|
71
|
+
* to be embedded via `h.submodel`. */
|
|
72
|
+
export const view = defineView((model, viewInputs) => {
|
|
73
|
+
const h = html();
|
|
74
|
+
const { id, isChecked } = model;
|
|
75
|
+
const { isDisabled = false, name, value: formValue = 'on' } = viewInputs;
|
|
76
|
+
const handleKeyUp = (key) => M.value(key).pipe(M.when(' ', () => Option.some(Toggled())), M.orElse(() => Option.none()));
|
|
77
|
+
const checkedAttributes = isChecked ? [h.DataAttribute('checked', '')] : [];
|
|
78
|
+
const disabledAttributes = isDisabled
|
|
79
|
+
? [h.AriaDisabled(true), h.DataAttribute('disabled', '')]
|
|
80
|
+
: [];
|
|
81
|
+
const buttonAttributes = [
|
|
82
|
+
h.Role('switch'),
|
|
83
|
+
h.AriaChecked(isChecked),
|
|
84
|
+
h.AriaLabelledBy(labelId(id)),
|
|
85
|
+
h.AriaDescribedBy(descriptionId(id)),
|
|
86
|
+
h.Tabindex(0),
|
|
87
|
+
...checkedAttributes,
|
|
88
|
+
...disabledAttributes,
|
|
89
|
+
...(isDisabled
|
|
90
|
+
? []
|
|
91
|
+
: [h.OnClick(Toggled()), h.OnKeyUpPreventDefault(handleKeyUp)]),
|
|
92
|
+
];
|
|
93
|
+
const labelAttributes = [
|
|
94
|
+
h.Id(labelId(id)),
|
|
95
|
+
...(isDisabled ? [] : [h.OnClick(Toggled())]),
|
|
96
|
+
];
|
|
97
|
+
const descriptionAttributes = [h.Id(descriptionId(id))];
|
|
98
|
+
const hiddenInputAttributes = name
|
|
99
|
+
? [h.Type('hidden'), h.Name(name), h.Value(isChecked ? formValue : '')]
|
|
100
|
+
: [];
|
|
101
|
+
return viewInputs.toView({
|
|
102
|
+
button: childAttributes(buttonAttributes),
|
|
103
|
+
label: childAttributes(labelAttributes),
|
|
104
|
+
description: childAttributes(descriptionAttributes),
|
|
105
|
+
hiddenInput: childAttributes(hiddenInputAttributes),
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/switch/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,MAAM,EACN,UAAU,EACV,cAAc,EACd,IAAI,EACJ,KAAK,EACL,OAAO,EACP,UAAU,EACV,UAAU,EACV,cAAc,GACf,MAAM,YAAY,CAAA;AAEnB,YAAY,EACV,OAAO,EACP,UAAU,EACV,UAAU,EACV,gBAAgB,GACjB,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { init, update, setChecked, reflectChecked, view, Model, Message, OutMessage, SetChecked, ToggledChecked, } from './index.js';
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Effect, Option, Schema as S } from 'effect';
|
|
2
|
+
import * as Command from 'foldkit/command';
|
|
3
|
+
import { type ChildAttribute, type Html } from 'foldkit/html';
|
|
4
|
+
import { type Reflect2, type View as SubmodelView } from 'foldkit/submodel';
|
|
5
|
+
export { wrapIndex, findFirstEnabledIndex, keyToIndex } from '../keyboard.js';
|
|
6
|
+
/** Controls the tab list layout direction and which arrow keys navigate between tabs. */
|
|
7
|
+
export declare const Orientation: S.Literals<readonly ["Horizontal", "Vertical"]>;
|
|
8
|
+
export type Orientation = typeof Orientation.Type;
|
|
9
|
+
/** Controls whether tabs activate on focus (`Automatic`) or require an explicit selection (`Manual`). */
|
|
10
|
+
export declare const ActivationMode: S.Literals<readonly ["Automatic", "Manual"]>;
|
|
11
|
+
export type ActivationMode = typeof ActivationMode.Type;
|
|
12
|
+
/** Schema for the tabs component's state, tracking active/focused indices and activation mode. */
|
|
13
|
+
export declare const Model: S.Struct<{
|
|
14
|
+
readonly id: S.String;
|
|
15
|
+
readonly activeIndex: S.Number;
|
|
16
|
+
readonly focusedIndex: S.Number;
|
|
17
|
+
readonly activationMode: S.Literals<readonly ["Automatic", "Manual"]>;
|
|
18
|
+
}>;
|
|
19
|
+
export type Model = typeof Model.Type;
|
|
20
|
+
/** Sent when a tab is selected via click or keyboard. Updates both the active and focused indices. */
|
|
21
|
+
export declare const SelectedTab: import("foldkit/schema").CallableTaggedStruct<"SelectedTab", {
|
|
22
|
+
index: S.Number;
|
|
23
|
+
value: S.String;
|
|
24
|
+
}>;
|
|
25
|
+
/** Sent when a tab receives keyboard focus in `Manual` mode without being activated. */
|
|
26
|
+
export declare const FocusedTab: import("foldkit/schema").CallableTaggedStruct<"FocusedTab", {
|
|
27
|
+
index: S.Number;
|
|
28
|
+
}>;
|
|
29
|
+
/** Sent when the focus-tab command completes. */
|
|
30
|
+
export declare const CompletedFocusTab: import("foldkit/schema").CallableTaggedStruct<"CompletedFocusTab", {}>;
|
|
31
|
+
/** Union of all messages the tabs component can produce. */
|
|
32
|
+
export declare const Message: S.Union<[
|
|
33
|
+
typeof SelectedTab,
|
|
34
|
+
typeof FocusedTab,
|
|
35
|
+
typeof CompletedFocusTab
|
|
36
|
+
]>;
|
|
37
|
+
export type SelectedTab = typeof SelectedTab.Type;
|
|
38
|
+
export type FocusedTab = typeof FocusedTab.Type;
|
|
39
|
+
export type Message = typeof Message.Type;
|
|
40
|
+
/** Sent to the parent when a tab is committed via click or keyboard. Carries both the tab's value (typed as `Value` via `Tabs.create<Value>()`) and its index. Generic at the type level; the schema stores `value: string` and the factory's fenced cast types it as `Value`. */
|
|
41
|
+
export declare const Selected: import("foldkit/schema").CallableTaggedStruct<"Selected", {
|
|
42
|
+
value: S.String;
|
|
43
|
+
index: S.Number;
|
|
44
|
+
}>;
|
|
45
|
+
export type Selected<Value extends string = string> = Readonly<{
|
|
46
|
+
readonly _tag: 'Selected';
|
|
47
|
+
readonly value: Value;
|
|
48
|
+
readonly index: number;
|
|
49
|
+
}>;
|
|
50
|
+
/** Union of out-messages the tabs component can produce. Surfaced as the third element of `update`'s return tuple and pattern-matched by the parent. */
|
|
51
|
+
export declare const OutMessage: S.Union<readonly [import("foldkit/schema").CallableTaggedStruct<"Selected", {
|
|
52
|
+
value: S.String;
|
|
53
|
+
index: S.Number;
|
|
54
|
+
}>]>;
|
|
55
|
+
/** Generic over `Value extends string` so consumers using
|
|
56
|
+
* `Tabs.create<MyUnion>()` receive `value: MyUnion` in the
|
|
57
|
+
* `Selected` OutMessage. Defaults to `string`. */
|
|
58
|
+
export type OutMessage<Value extends string = string> = Selected<Value>;
|
|
59
|
+
/** Configuration for creating a tabs model with `init`. */
|
|
60
|
+
export type InitConfig = Readonly<{
|
|
61
|
+
id: string;
|
|
62
|
+
activeIndex?: number;
|
|
63
|
+
activationMode?: ActivationMode;
|
|
64
|
+
}>;
|
|
65
|
+
/** Creates an initial tabs model from a config. Defaults to first tab and automatic activation. */
|
|
66
|
+
export declare const init: (config: InitConfig) => Model;
|
|
67
|
+
/** Moves focus to the tab at the given index. */
|
|
68
|
+
export declare const FocusTab: Command.CommandDefinitionWithArgs<"FocusTab", {
|
|
69
|
+
id: S.String;
|
|
70
|
+
index: S.Number;
|
|
71
|
+
}, Effect.Effect<{
|
|
72
|
+
readonly _tag: "CompletedFocusTab";
|
|
73
|
+
}, never, never>>;
|
|
74
|
+
type UpdateReturn = readonly [
|
|
75
|
+
Model,
|
|
76
|
+
ReadonlyArray<Command.Command<Message>>,
|
|
77
|
+
Option.Option<OutMessage>
|
|
78
|
+
];
|
|
79
|
+
/** Processes a tabs message and returns the next model, commands, and an optional OutMessage. `Selected` fires when a tab is committed via click or keyboard. */
|
|
80
|
+
export declare const update: (model: Model, message: Message) => UpdateReturn;
|
|
81
|
+
/** Programmatically selects a tab. Emits a `Selected` OutMessage. */
|
|
82
|
+
export declare const selectTab: (model: Model, value: string, index: number) => UpdateReturn;
|
|
83
|
+
/** Reflects an externally-sourced active tab onto the model without
|
|
84
|
+
* emitting an OutMessage or running the focus command. Use this to mirror
|
|
85
|
+
* external truth (a deep link, restored storage) onto the active tab.
|
|
86
|
+
* Contrast with `selectTab`, which represents a user or programmatic
|
|
87
|
+
* *choice*: it focuses the tab and emits `Selected`. Takes the tab `value`
|
|
88
|
+
* plus the `options` list (mirroring `RadioGroup.select`) because Tabs
|
|
89
|
+
* stores the active *index* internally, so the value is resolved to an
|
|
90
|
+
* index. A value not present in `options` is a no-op. Returns the model
|
|
91
|
+
* directly because it produces no commands and no OutMessage. */
|
|
92
|
+
export declare const reflectSelectedTab: Reflect2<Model, string, ReadonlyArray<string>>;
|
|
93
|
+
/** Per-tab render info passed to the consumer's `toView`. Generic over
|
|
94
|
+
* `Value extends string`: when `Tabs.create<MyUnion>()` is declared,
|
|
95
|
+
* `tab.value` is typed `MyUnion` so the consumer can switch on it without
|
|
96
|
+
* casting. */
|
|
97
|
+
export type TabInfo<Value extends string = string> = Readonly<{
|
|
98
|
+
value: Value;
|
|
99
|
+
index: number;
|
|
100
|
+
isActive: boolean;
|
|
101
|
+
isFocused: boolean;
|
|
102
|
+
isDisabled: boolean;
|
|
103
|
+
tab: ReadonlyArray<ChildAttribute>;
|
|
104
|
+
panel: ReadonlyArray<ChildAttribute>;
|
|
105
|
+
}>;
|
|
106
|
+
/** Render-time payload published to the consumer's `toView`.
|
|
107
|
+
*
|
|
108
|
+
* - `tablist`: ARIA + role attributes for the wrapping tablist element.
|
|
109
|
+
* - `tabs`: one entry per tab in `viewInputs.tabs`, in the same order, with
|
|
110
|
+
* the tab button's attribute bundle, the panel's attribute bundle,
|
|
111
|
+
* and derived state.
|
|
112
|
+
* - `activeIndex`: the currently-active tab index, convenient when the
|
|
113
|
+
* consumer wants to render only the active panel (vs all panels with
|
|
114
|
+
* `Hidden` for transitions). */
|
|
115
|
+
export type RenderInfo<Value extends string = string> = Readonly<{
|
|
116
|
+
tablist: ReadonlyArray<ChildAttribute>;
|
|
117
|
+
tabs: ReadonlyArray<TabInfo<Value>>;
|
|
118
|
+
activeIndex: number;
|
|
119
|
+
}>;
|
|
120
|
+
/** Per-render view inputs passed to `view` via `h.submodel`'s `viewInputs` field.
|
|
121
|
+
* Generic over `Value extends string` so consumers using
|
|
122
|
+
* `Tabs.create<MyUnion>()` receive `tab.value: MyUnion` in `toView`
|
|
123
|
+
* and `(value: MyUnion, index) => boolean` in `isTabDisabled`, without
|
|
124
|
+
* casting. */
|
|
125
|
+
export type ViewInputs<Value extends string = string> = Readonly<{
|
|
126
|
+
tabs: ReadonlyArray<Value>;
|
|
127
|
+
ariaLabel: string;
|
|
128
|
+
toView: (render: RenderInfo<Value>) => Html;
|
|
129
|
+
isTabDisabled?: (value: Value, index: number) => boolean;
|
|
130
|
+
orientation?: Orientation;
|
|
131
|
+
}>;
|
|
132
|
+
/** Pairs the tabs `view`, `update`, and `selectTab` behind a single
|
|
133
|
+
* Value-typed entry point. Declare once at module scope so consumers
|
|
134
|
+
* receive `tab.value: Value` in `toView` without an `as` cast:
|
|
135
|
+
*
|
|
136
|
+
* ```ts
|
|
137
|
+
* const DemoTabs = Tabs.create<DemoTab>()
|
|
138
|
+
*
|
|
139
|
+
* // In view:
|
|
140
|
+
* h.submodel({ view: DemoTabs.view, ... })
|
|
141
|
+
*
|
|
142
|
+
* // In update:
|
|
143
|
+
* const [next, commands] = DemoTabs.update(model, message)
|
|
144
|
+
* ```
|
|
145
|
+
*
|
|
146
|
+
* The internal view stays typed `ReadonlyArray<string>`; consumers can
|
|
147
|
+
* pass a `ReadonlyArray<MyUnion>` (assignable) and the fenced cast inside
|
|
148
|
+
* `create` types `TabInfo.value` as `MyUnion`. */
|
|
149
|
+
export declare const create: <Value extends string = string>() => Readonly<{
|
|
150
|
+
view: SubmodelView<Model, Message, ViewInputs<Value>>;
|
|
151
|
+
update: (model: Model, message: Message) => readonly [Model, ReadonlyArray<Command.Command<Message>>, Option.Option<OutMessage<Value>>];
|
|
152
|
+
selectTab: (model: Model, value: Value, index: number) => readonly [Model, ReadonlyArray<Command.Command<Message>>, Option.Option<OutMessage<Value>>];
|
|
153
|
+
reflectSelectedTab: Reflect2<Model, Value, ReadonlyArray<Value>>;
|
|
154
|
+
}>;
|
|
155
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tabs/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { Array, Effect, Function, Match as M, Option, Schema as S, String, pipe, } from 'effect';
|
|
2
|
+
import * as Command from 'foldkit/command';
|
|
3
|
+
import * as Dom from 'foldkit/dom';
|
|
4
|
+
import { childAttributes, html, } from 'foldkit/html';
|
|
5
|
+
import { m } from 'foldkit/message';
|
|
6
|
+
import { evo } from 'foldkit/struct';
|
|
7
|
+
import { defineView, } from 'foldkit/submodel';
|
|
8
|
+
import { keyToIndex } from '../keyboard.js';
|
|
9
|
+
export { wrapIndex, findFirstEnabledIndex, keyToIndex } from '../keyboard.js';
|
|
10
|
+
// MODEL
|
|
11
|
+
/** Controls the tab list layout direction and which arrow keys navigate between tabs. */
|
|
12
|
+
export const Orientation = S.Literals(['Horizontal', 'Vertical']);
|
|
13
|
+
/** Controls whether tabs activate on focus (`Automatic`) or require an explicit selection (`Manual`). */
|
|
14
|
+
export const ActivationMode = S.Literals(['Automatic', 'Manual']);
|
|
15
|
+
/** Schema for the tabs component's state, tracking active/focused indices and activation mode. */
|
|
16
|
+
export const Model = S.Struct({
|
|
17
|
+
id: S.String,
|
|
18
|
+
activeIndex: S.Number,
|
|
19
|
+
focusedIndex: S.Number,
|
|
20
|
+
activationMode: ActivationMode,
|
|
21
|
+
});
|
|
22
|
+
// MESSAGE
|
|
23
|
+
/** Sent when a tab is selected via click or keyboard. Updates both the active and focused indices. */
|
|
24
|
+
export const SelectedTab = m('SelectedTab', {
|
|
25
|
+
index: S.Number,
|
|
26
|
+
value: S.String,
|
|
27
|
+
});
|
|
28
|
+
/** Sent when a tab receives keyboard focus in `Manual` mode without being activated. */
|
|
29
|
+
export const FocusedTab = m('FocusedTab', { index: S.Number });
|
|
30
|
+
/** Sent when the focus-tab command completes. */
|
|
31
|
+
export const CompletedFocusTab = m('CompletedFocusTab');
|
|
32
|
+
/** Union of all messages the tabs component can produce. */
|
|
33
|
+
export const Message = S.Union([SelectedTab, FocusedTab, CompletedFocusTab]);
|
|
34
|
+
// OUT MESSAGE
|
|
35
|
+
/** Sent to the parent when a tab is committed via click or keyboard. Carries both the tab's value (typed as `Value` via `Tabs.create<Value>()`) and its index. Generic at the type level; the schema stores `value: string` and the factory's fenced cast types it as `Value`. */
|
|
36
|
+
export const Selected = m('Selected', {
|
|
37
|
+
value: S.String,
|
|
38
|
+
index: S.Number,
|
|
39
|
+
});
|
|
40
|
+
/** Union of out-messages the tabs component can produce. Surfaced as the third element of `update`'s return tuple and pattern-matched by the parent. */
|
|
41
|
+
export const OutMessage = S.Union([Selected]);
|
|
42
|
+
/** Creates an initial tabs model from a config. Defaults to first tab and automatic activation. */
|
|
43
|
+
export const init = (config) => {
|
|
44
|
+
const activeIndex = config.activeIndex ?? 0;
|
|
45
|
+
return {
|
|
46
|
+
id: config.id,
|
|
47
|
+
activeIndex,
|
|
48
|
+
focusedIndex: activeIndex,
|
|
49
|
+
activationMode: config.activationMode ?? 'Automatic',
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
// UPDATE
|
|
53
|
+
const tabId = (id, index) => `${id}-tab-${index}`;
|
|
54
|
+
const tabPanelId = (id, index) => `${id}-panel-${index}`;
|
|
55
|
+
/** Moves focus to the tab at the given index. */
|
|
56
|
+
export const FocusTab = Command.define('FocusTab', { id: S.String, index: S.Number }, CompletedFocusTab)(({ id, index }) => Dom.focus(`#${tabId(id, index)}`).pipe(Effect.ignore, Effect.as(CompletedFocusTab())));
|
|
57
|
+
/** Processes a tabs message and returns the next model, commands, and an optional OutMessage. `Selected` fires when a tab is committed via click or keyboard. */
|
|
58
|
+
export const update = (model, message) => M.value(message).pipe(M.withReturnType(), M.tagsExhaustive({
|
|
59
|
+
SelectedTab: ({ index, value }) => [
|
|
60
|
+
evo(model, {
|
|
61
|
+
activeIndex: () => index,
|
|
62
|
+
focusedIndex: () => index,
|
|
63
|
+
}),
|
|
64
|
+
[FocusTab({ id: model.id, index })],
|
|
65
|
+
Option.some(Selected({ value, index })),
|
|
66
|
+
],
|
|
67
|
+
FocusedTab: ({ index }) => [
|
|
68
|
+
evo(model, { focusedIndex: () => index }),
|
|
69
|
+
[FocusTab({ id: model.id, index })],
|
|
70
|
+
Option.none(),
|
|
71
|
+
],
|
|
72
|
+
CompletedFocusTab: () => [model, [], Option.none()],
|
|
73
|
+
}));
|
|
74
|
+
/** Programmatically selects a tab. Emits a `Selected` OutMessage. */
|
|
75
|
+
export const selectTab = (model, value, index) => update(model, SelectedTab({ index, value }));
|
|
76
|
+
/** Reflects an externally-sourced active tab onto the model without
|
|
77
|
+
* emitting an OutMessage or running the focus command. Use this to mirror
|
|
78
|
+
* external truth (a deep link, restored storage) onto the active tab.
|
|
79
|
+
* Contrast with `selectTab`, which represents a user or programmatic
|
|
80
|
+
* *choice*: it focuses the tab and emits `Selected`. Takes the tab `value`
|
|
81
|
+
* plus the `options` list (mirroring `RadioGroup.select`) because Tabs
|
|
82
|
+
* stores the active *index* internally, so the value is resolved to an
|
|
83
|
+
* index. A value not present in `options` is a no-op. Returns the model
|
|
84
|
+
* directly because it produces no commands and no OutMessage. */
|
|
85
|
+
export const reflectSelectedTab = Function.dual(3, (model, value, options) => pipe(options, Array.findFirstIndex(option => option === value), Option.match({
|
|
86
|
+
onNone: () => model,
|
|
87
|
+
onSome: index => evo(model, { activeIndex: () => index, focusedIndex: () => index }),
|
|
88
|
+
})));
|
|
89
|
+
const internalView = defineView((model, viewInputs) => {
|
|
90
|
+
const h = html();
|
|
91
|
+
const { id, activationMode, focusedIndex, activeIndex } = model;
|
|
92
|
+
const { tabs, ariaLabel, toView, isTabDisabled, orientation = 'Horizontal', } = viewInputs;
|
|
93
|
+
const isDisabled = (index) => !!isTabDisabled &&
|
|
94
|
+
pipe(tabs, Array.get(index), Option.exists(tab => isTabDisabled(tab, index)));
|
|
95
|
+
const { nextKey, previousKey } = M.value(orientation).pipe(M.when('Horizontal', () => ({
|
|
96
|
+
nextKey: 'ArrowRight',
|
|
97
|
+
previousKey: 'ArrowLeft',
|
|
98
|
+
})), M.when('Vertical', () => ({
|
|
99
|
+
nextKey: 'ArrowDown',
|
|
100
|
+
previousKey: 'ArrowUp',
|
|
101
|
+
})), M.exhaustive);
|
|
102
|
+
const resolveKeyIndex = keyToIndex(nextKey, previousKey, tabs.length, focusedIndex, isDisabled);
|
|
103
|
+
const tabSelectedAt = (index) => pipe(tabs, Array.get(index), Option.map(value => SelectedTab({ index, value })));
|
|
104
|
+
const handleAutomaticKeyDown = (key) => M.value(key).pipe(M.whenOr(nextKey, previousKey, 'Home', 'End', 'PageUp', 'PageDown', () => tabSelectedAt(resolveKeyIndex(key))), M.whenOr('Enter', ' ', () => tabSelectedAt(focusedIndex)), M.orElse(() => Option.none()));
|
|
105
|
+
const handleManualKeyDown = (key) => M.value(key).pipe(M.whenOr(nextKey, previousKey, 'Home', 'End', 'PageUp', 'PageDown', () => Option.some(FocusedTab({ index: resolveKeyIndex(key) }))), M.whenOr('Enter', ' ', () => tabSelectedAt(focusedIndex)), M.orElse(() => Option.none()));
|
|
106
|
+
const handleKeyDown = (key) => M.value(activationMode).pipe(M.when('Automatic', () => handleAutomaticKeyDown(key)), M.when('Manual', () => handleManualKeyDown(key)), M.exhaustive);
|
|
107
|
+
const tabInfos = Array.map(tabs, (value, index) => {
|
|
108
|
+
const isActive = index === activeIndex;
|
|
109
|
+
const isFocused = index === focusedIndex;
|
|
110
|
+
const isTabDisabledNow = isDisabled(index);
|
|
111
|
+
const tabAttributes = [
|
|
112
|
+
h.Id(tabId(id, index)),
|
|
113
|
+
h.Role('tab'),
|
|
114
|
+
h.Type('button'),
|
|
115
|
+
h.AriaSelected(isActive),
|
|
116
|
+
h.AriaControls(tabPanelId(id, index)),
|
|
117
|
+
h.Tabindex(isFocused ? 0 : -1),
|
|
118
|
+
...(isActive ? [h.DataAttribute('selected', '')] : []),
|
|
119
|
+
...(isTabDisabledNow
|
|
120
|
+
? [
|
|
121
|
+
h.Disabled(true),
|
|
122
|
+
h.AriaDisabled(true),
|
|
123
|
+
h.DataAttribute('disabled', ''),
|
|
124
|
+
]
|
|
125
|
+
: [h.OnClick(SelectedTab({ index, value }))]),
|
|
126
|
+
h.OnKeyDownPreventDefault(handleKeyDown),
|
|
127
|
+
];
|
|
128
|
+
const panelAttributes = [
|
|
129
|
+
h.Id(tabPanelId(id, index)),
|
|
130
|
+
h.Role('tabpanel'),
|
|
131
|
+
h.AriaLabelledBy(tabId(id, index)),
|
|
132
|
+
h.Tabindex(isActive ? 0 : -1),
|
|
133
|
+
...(isActive ? [h.DataAttribute('selected', '')] : []),
|
|
134
|
+
];
|
|
135
|
+
return {
|
|
136
|
+
value,
|
|
137
|
+
index,
|
|
138
|
+
isActive,
|
|
139
|
+
isFocused,
|
|
140
|
+
isDisabled: isTabDisabledNow,
|
|
141
|
+
tab: childAttributes(tabAttributes),
|
|
142
|
+
panel: childAttributes(panelAttributes),
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
const tablistAttributes = [
|
|
146
|
+
h.Role('tablist'),
|
|
147
|
+
h.AriaOrientation(String.toLowerCase(orientation)),
|
|
148
|
+
h.AriaLabel(ariaLabel),
|
|
149
|
+
];
|
|
150
|
+
return toView({
|
|
151
|
+
tablist: childAttributes(tablistAttributes),
|
|
152
|
+
tabs: tabInfos,
|
|
153
|
+
activeIndex,
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
/** Pairs the tabs `view`, `update`, and `selectTab` behind a single
|
|
157
|
+
* Value-typed entry point. Declare once at module scope so consumers
|
|
158
|
+
* receive `tab.value: Value` in `toView` without an `as` cast:
|
|
159
|
+
*
|
|
160
|
+
* ```ts
|
|
161
|
+
* const DemoTabs = Tabs.create<DemoTab>()
|
|
162
|
+
*
|
|
163
|
+
* // In view:
|
|
164
|
+
* h.submodel({ view: DemoTabs.view, ... })
|
|
165
|
+
*
|
|
166
|
+
* // In update:
|
|
167
|
+
* const [next, commands] = DemoTabs.update(model, message)
|
|
168
|
+
* ```
|
|
169
|
+
*
|
|
170
|
+
* The internal view stays typed `ReadonlyArray<string>`; consumers can
|
|
171
|
+
* pass a `ReadonlyArray<MyUnion>` (assignable) and the fenced cast inside
|
|
172
|
+
* `create` types `TabInfo.value` as `MyUnion`. */
|
|
173
|
+
export const create = () => {
|
|
174
|
+
const cast = (result) =>
|
|
175
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
176
|
+
result;
|
|
177
|
+
return {
|
|
178
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
179
|
+
view: internalView,
|
|
180
|
+
update: (model, message) => cast(update(model, message)),
|
|
181
|
+
selectTab: (model, value, index) => cast(selectTab(model, value, index)),
|
|
182
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
183
|
+
reflectSelectedTab: reflectSelectedTab,
|
|
184
|
+
};
|
|
185
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { init, create, Model, Message, OutMessage, Selected, SelectedTab, FocusedTab, CompletedFocusTab, FocusTab, } from './index.js';
|
|
2
|
+
export type { Orientation, ActivationMode, InitConfig, ViewInputs, RenderInfo, TabInfo, } from './index.js';
|
|
3
|
+
//# sourceMappingURL=public.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/tabs/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,MAAM,EACN,KAAK,EACL,OAAO,EACP,UAAU,EACV,QAAQ,EACR,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,QAAQ,GACT,MAAM,YAAY,CAAA;AAEnB,YAAY,EACV,WAAW,EACX,cAAc,EACd,UAAU,EACV,UAAU,EACV,UAAU,EACV,OAAO,GACR,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { init, create, Model, Message, OutMessage, Selected, SelectedTab, FocusedTab, CompletedFocusTab, FocusTab, } from './index.js';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Schema as S } from 'effect';
|
|
2
|
+
import * as Command from 'foldkit/command';
|
|
3
|
+
import { type Html } from 'foldkit/html';
|
|
4
|
+
export declare const Model: S.Struct<{
|
|
5
|
+
readonly isEnabled: S.Boolean;
|
|
6
|
+
readonly dialog: S.Struct<{
|
|
7
|
+
readonly id: S.String;
|
|
8
|
+
readonly isOpen: S.Boolean;
|
|
9
|
+
readonly isAnimated: S.Boolean;
|
|
10
|
+
readonly animation: S.Struct<{
|
|
11
|
+
readonly id: S.String;
|
|
12
|
+
readonly isShowing: S.Boolean;
|
|
13
|
+
readonly transitionState: S.Literals<readonly ["Idle", "EnterStart", "EnterAnimating", "LeaveStart", "LeaveAnimating"]>;
|
|
14
|
+
}>;
|
|
15
|
+
readonly maybeFocusSelector: S.Option<S.String>;
|
|
16
|
+
}>;
|
|
17
|
+
}>;
|
|
18
|
+
export type Model = typeof Model.Type;
|
|
19
|
+
export declare const ClickedToggle: import("foldkit/schema").CallableTaggedStruct<"ClickedToggle", {}>;
|
|
20
|
+
export declare const ClickedSubmit: import("foldkit/schema").CallableTaggedStruct<"ClickedSubmit", {}>;
|
|
21
|
+
export declare const GotDialogMessage: import("foldkit/schema").CallableTaggedStruct<"GotDialogMessage", {
|
|
22
|
+
message: S.Union<[import("foldkit/schema").CallableTaggedStruct<"RequestedOpen", {}>, import("foldkit/schema").CallableTaggedStruct<"RequestedClose", {}>, import("foldkit/schema").CallableTaggedStruct<"CompletedShowDialog", {}>, import("foldkit/schema").CallableTaggedStruct<"CompletedCloseDialog", {}>, import("foldkit/schema").CallableTaggedStruct<"GotAnimationMessage", {
|
|
23
|
+
message: S.Union<[import("foldkit/schema").CallableTaggedStruct<"Showed", {}>, import("foldkit/schema").CallableTaggedStruct<"Hid", {}>, import("foldkit/schema").CallableTaggedStruct<"AdvancedAnimationFrame", {}>, import("foldkit/schema").CallableTaggedStruct<"EndedAnimation", {}>]>;
|
|
24
|
+
}>]>;
|
|
25
|
+
}>;
|
|
26
|
+
export declare const Message: S.Union<readonly [import("foldkit/schema").CallableTaggedStruct<"ClickedToggle", {}>, import("foldkit/schema").CallableTaggedStruct<"ClickedSubmit", {}>, import("foldkit/schema").CallableTaggedStruct<"GotDialogMessage", {
|
|
27
|
+
message: S.Union<[import("foldkit/schema").CallableTaggedStruct<"RequestedOpen", {}>, import("foldkit/schema").CallableTaggedStruct<"RequestedClose", {}>, import("foldkit/schema").CallableTaggedStruct<"CompletedShowDialog", {}>, import("foldkit/schema").CallableTaggedStruct<"CompletedCloseDialog", {}>, import("foldkit/schema").CallableTaggedStruct<"GotAnimationMessage", {
|
|
28
|
+
message: S.Union<[import("foldkit/schema").CallableTaggedStruct<"Showed", {}>, import("foldkit/schema").CallableTaggedStruct<"Hid", {}>, import("foldkit/schema").CallableTaggedStruct<"AdvancedAnimationFrame", {}>, import("foldkit/schema").CallableTaggedStruct<"EndedAnimation", {}>]>;
|
|
29
|
+
}>]>;
|
|
30
|
+
}>]>;
|
|
31
|
+
export type Message = typeof Message.Type;
|
|
32
|
+
export declare const initialModel: Model;
|
|
33
|
+
export declare const update: (model: Model, message: Message) => readonly [Model, ReadonlyArray<Command.Command<Message>>];
|
|
34
|
+
/** Plain view, no dialog wrapper. */
|
|
35
|
+
export declare const view: (model: Model) => Html;
|
|
36
|
+
/** View with submit button inside a dialog's panel. */
|
|
37
|
+
export declare const viewWithDialog: (model: Model) => Html;
|
|
38
|
+
//# sourceMappingURL=disabledButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disabledButton.d.ts","sourceRoot":"","sources":["../../../src/test/apps/disabledButton.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AAChD,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAA;AAC1C,OAAO,EAAE,KAAK,IAAI,EAAQ,MAAM,cAAc,CAAA;AAO9C,eAAO,MAAM,KAAK;;;;;;;;;;;;;EAGhB,CAAA;AACF,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAA;AAIrC,eAAO,MAAM,aAAa,oEAAqB,CAAA;AAC/C,eAAO,MAAM,aAAa,oEAAqB,CAAA;AAC/C,eAAO,MAAM,gBAAgB;;;;EAE3B,CAAA;AAEF,eAAO,MAAM,OAAO;;;;IAA4D,CAAA;AAChF,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAIzC,eAAO,MAAM,YAAY,EAAE,KAG1B,CAAA;AAID,eAAO,MAAM,MAAM,GACjB,OAAO,KAAK,EACZ,SAAS,OAAO,KACf,SAAS,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAqBxD,CAAA;AAgBH,qCAAqC;AACrC,eAAO,MAAM,IAAI,GAAI,OAAO,KAAK,KAAG,IAUnC,CAAA;AAED,uDAAuD;AACvD,eAAO,MAAM,cAAc,GAAI,OAAO,KAAK,KAAG,IA2B7C,CAAA"}
|