@foldkit/ui 0.127.0 → 0.128.1
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 +2 -2
- package/dist/calendar/index.d.ts +17 -23
- package/dist/calendar/index.d.ts.map +1 -1
- package/dist/calendar/index.js +16 -25
- package/dist/calendar/public.d.ts +1 -1
- package/dist/calendar/public.d.ts.map +1 -1
- package/dist/calendar/public.js +1 -1
- package/dist/checkbox/index.d.ts +47 -104
- package/dist/checkbox/index.d.ts.map +1 -1
- package/dist/checkbox/index.js +48 -87
- package/dist/checkbox/public.d.ts +2 -2
- package/dist/checkbox/public.d.ts.map +1 -1
- package/dist/checkbox/public.js +1 -1
- package/dist/combobox/multi.d.ts +19 -30
- package/dist/combobox/multi.d.ts.map +1 -1
- package/dist/combobox/multi.js +32 -53
- package/dist/combobox/public.d.ts +2 -2
- package/dist/combobox/public.d.ts.map +1 -1
- package/dist/combobox/public.js +1 -1
- package/dist/combobox/shared.d.ts +59 -35
- package/dist/combobox/shared.d.ts.map +1 -1
- package/dist/combobox/shared.js +58 -45
- package/dist/combobox/single.d.ts +36 -48
- package/dist/combobox/single.d.ts.map +1 -1
- package/dist/combobox/single.js +49 -76
- package/dist/datePicker/index.d.ts +33 -41
- package/dist/datePicker/index.d.ts.map +1 -1
- package/dist/datePicker/index.js +31 -40
- package/dist/datePicker/public.d.ts +1 -1
- package/dist/datePicker/public.d.ts.map +1 -1
- package/dist/datePicker/public.js +1 -1
- package/dist/disclosure/index.d.ts +49 -113
- package/dist/disclosure/index.d.ts.map +1 -1
- package/dist/disclosure/index.js +35 -92
- package/dist/disclosure/public.d.ts +2 -2
- package/dist/disclosure/public.d.ts.map +1 -1
- package/dist/disclosure/public.js +1 -1
- package/dist/listbox/multi.d.ts +7 -21
- package/dist/listbox/multi.d.ts.map +1 -1
- package/dist/listbox/multi.js +12 -36
- package/dist/listbox/public.d.ts +1 -1
- package/dist/listbox/public.d.ts.map +1 -1
- package/dist/listbox/shared.d.ts +28 -20
- package/dist/listbox/shared.d.ts.map +1 -1
- package/dist/listbox/shared.js +6 -7
- package/dist/listbox/single.d.ts +17 -27
- package/dist/listbox/single.d.ts.map +1 -1
- package/dist/listbox/single.js +18 -30
- package/dist/radioGroup/index.d.ts +57 -142
- package/dist/radioGroup/index.d.ts.map +1 -1
- package/dist/radioGroup/index.js +44 -119
- package/dist/radioGroup/public.d.ts +2 -2
- package/dist/radioGroup/public.d.ts.map +1 -1
- package/dist/radioGroup/public.js +1 -1
- package/dist/slider/index.d.ts +51 -27
- package/dist/slider/index.d.ts.map +1 -1
- package/dist/slider/index.js +66 -66
- package/dist/slider/public.d.ts +1 -1
- package/dist/slider/public.d.ts.map +1 -1
- package/dist/slider/public.js +1 -1
- package/dist/switch/index.d.ts +45 -92
- package/dist/switch/index.d.ts.map +1 -1
- package/dist/switch/index.js +36 -81
- package/dist/switch/public.d.ts +2 -2
- package/dist/switch/public.d.ts.map +1 -1
- package/dist/switch/public.js +1 -1
- package/dist/tabs/index.d.ts +33 -33
- package/dist/tabs/index.d.ts.map +1 -1
- package/dist/tabs/index.js +36 -49
- package/dist/toast/index.d.ts +1 -0
- package/dist/toast/index.d.ts.map +1 -1
- package/dist/toast/index.js +1 -0
- package/dist/toast/public.d.ts +2 -0
- package/dist/toast/public.d.ts.map +1 -1
- package/dist/toast/public.js +1 -0
- package/dist/toast/test.d.ts +41 -0
- package/dist/toast/test.d.ts.map +1 -0
- package/dist/toast/test.js +36 -0
- package/package.json +5 -5
package/dist/slider/index.js
CHANGED
|
@@ -9,23 +9,30 @@ import * as Subscription from 'foldkit/subscription';
|
|
|
9
9
|
const Idle = ts('Idle');
|
|
10
10
|
const Dragging = ts('Dragging', { originValue: S.Number });
|
|
11
11
|
const DragState = S.Union([Idle, Dragging]);
|
|
12
|
-
/** Schema for the slider component's state.
|
|
13
|
-
*
|
|
12
|
+
/** Schema for the slider component's private interaction state. The current
|
|
13
|
+
* value is owned by the parent and passed in via `ViewInputs.value`, so it is
|
|
14
|
+
* not stored here. `min`/`max`/`step` are configuration the drag subscription
|
|
15
|
+
* reads to map pointer positions into values. `dragState` tracks the active
|
|
16
|
+
* drag phase and captures the pre-drag value so Escape can restore it. */
|
|
14
17
|
export const Model = S.Struct({
|
|
15
18
|
id: S.String,
|
|
16
|
-
value: S.Number,
|
|
17
19
|
min: S.Number,
|
|
18
20
|
max: S.Number,
|
|
19
21
|
step: S.Number,
|
|
20
22
|
dragState: DragState,
|
|
21
23
|
});
|
|
22
24
|
// MESSAGE
|
|
23
|
-
/** The user pressed the thumb. Starts a drag without changing the value.
|
|
24
|
-
|
|
25
|
+
/** The user pressed the thumb. Starts a drag without changing the value. The
|
|
26
|
+
* view supplies `originValue`, the current value, so Escape can restore it. */
|
|
27
|
+
export const PressedThumb = m('PressedThumb', { originValue: S.Number });
|
|
25
28
|
/** The user pressed the track. Starts a drag and snaps the value to the
|
|
26
29
|
* cursor position. Ignored while already dragging, which absorbs the bubble
|
|
27
|
-
* from a thumb press so the value is not shifted.
|
|
28
|
-
|
|
30
|
+
* from a thumb press so the value is not shifted. `originValue` is the current
|
|
31
|
+
* value the drag restores to on Escape. */
|
|
32
|
+
export const PressedPointer = m('PressedPointer', {
|
|
33
|
+
value: S.Number,
|
|
34
|
+
originValue: S.Number,
|
|
35
|
+
});
|
|
29
36
|
/** The pointer moved during a drag, producing a new snapped value from the
|
|
30
37
|
* cursor position within the track. */
|
|
31
38
|
export const MovedDragPointer = m('MovedDragPointer', { value: S.Number });
|
|
@@ -33,7 +40,8 @@ export const MovedDragPointer = m('MovedDragPointer', { value: S.Number });
|
|
|
33
40
|
export const ReleasedDragPointer = m('ReleasedDragPointer');
|
|
34
41
|
/** Escape was pressed during a drag. Restores the value from the drag origin. */
|
|
35
42
|
export const CancelledDrag = m('CancelledDrag');
|
|
36
|
-
/** The user pressed a keyboard navigation key on the focused thumb.
|
|
43
|
+
/** The user pressed a keyboard navigation key on the focused thumb. The view
|
|
44
|
+
* supplies `value`, the current value, to compute the next one from. */
|
|
37
45
|
export const PressedKeyboardNavigation = m('PressedKeyboardNavigation', {
|
|
38
46
|
direction: S.Literals([
|
|
39
47
|
'StepDecrement',
|
|
@@ -43,6 +51,7 @@ export const PressedKeyboardNavigation = m('PressedKeyboardNavigation', {
|
|
|
43
51
|
'Min',
|
|
44
52
|
'Max',
|
|
45
53
|
]),
|
|
54
|
+
value: S.Number,
|
|
46
55
|
});
|
|
47
56
|
/** Union of all messages the slider component can produce. */
|
|
48
57
|
export const Message = S.Union([
|
|
@@ -60,11 +69,10 @@ export const Message = S.Union([
|
|
|
60
69
|
export const ChangedValue = m('ChangedValue', { value: S.Number });
|
|
61
70
|
/** Union of all out-messages the slider component can emit to its parent. */
|
|
62
71
|
export const OutMessage = S.Union([ChangedValue]);
|
|
63
|
-
/** Creates an initial slider model from a config. The
|
|
64
|
-
*
|
|
72
|
+
/** Creates an initial slider model from a config. The value lives in the
|
|
73
|
+
* parent Model; initialize it there and snap it with {@link snapAndClamp}. */
|
|
65
74
|
export const init = (config) => ({
|
|
66
75
|
id: config.id,
|
|
67
|
-
value: snapAndClamp(config.initialValue, config.min, config.max, config.step),
|
|
68
76
|
min: config.min,
|
|
69
77
|
max: config.max,
|
|
70
78
|
step: config.step,
|
|
@@ -83,43 +91,37 @@ const roundToStepPrecision = (value, step) => {
|
|
|
83
91
|
return Number(value.toFixed(decimals));
|
|
84
92
|
};
|
|
85
93
|
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
86
|
-
|
|
94
|
+
/** Snaps a value to the nearest step and clamps it into `[min, max]`. Exported
|
|
95
|
+
* so a parent can conform the value it owns to the slider's range, for example
|
|
96
|
+
* when seeding the initial value or reacting to an external update. */
|
|
97
|
+
export const snapAndClamp = (value, min, max, step) => {
|
|
87
98
|
const snapped = min + Math.round((value - min) / step) * step;
|
|
88
99
|
return roundToStepPrecision(clamp(snapped, min, max), step);
|
|
89
100
|
};
|
|
90
101
|
/** Computes the fraction (0–1) of a value between min and max. Returns 0 when
|
|
91
102
|
* the range has zero width. */
|
|
92
|
-
export const fractionOfValue = (
|
|
93
|
-
const range =
|
|
103
|
+
export const fractionOfValue = (value, min, max) => {
|
|
104
|
+
const range = max - min;
|
|
94
105
|
if (range <= 0) {
|
|
95
106
|
return 0;
|
|
96
107
|
}
|
|
97
108
|
else {
|
|
98
|
-
return clamp((
|
|
109
|
+
return clamp((value - min) / range, 0, 1);
|
|
99
110
|
}
|
|
100
111
|
};
|
|
101
112
|
const PAGE_STEP_MULTIPLIER = 10;
|
|
102
|
-
const nextValueForDirection = (
|
|
113
|
+
const nextValueForDirection = (value, min, max, step, direction) => M.value(direction).pipe(M.withReturnType(), M.when('StepIncrement', () => snapAndClamp(value + step, min, max, step)), M.when('StepDecrement', () => snapAndClamp(value - step, min, max, step)), M.when('PageIncrement', () => snapAndClamp(value + step * PAGE_STEP_MULTIPLIER, min, max, step)), M.when('PageDecrement', () => snapAndClamp(value - step * PAGE_STEP_MULTIPLIER, min, max, step)), M.when('Min', () => min), M.when('Max', () => max), M.exhaustive);
|
|
103
114
|
const withUpdateReturn = M.withReturnType();
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
return [
|
|
110
|
-
evo(model, { value: () => nextValue }),
|
|
111
|
-
commands,
|
|
112
|
-
Option.some(ChangedValue({ value: nextValue })),
|
|
113
|
-
];
|
|
114
|
-
}
|
|
115
|
-
};
|
|
115
|
+
const changedValueOption = (currentValue, nextValue) => nextValue === currentValue
|
|
116
|
+
? Option.none()
|
|
117
|
+
: Option.some(ChangedValue({ value: nextValue }));
|
|
116
118
|
/** Processes a slider message and returns the next model, commands, and an
|
|
117
|
-
* optional out-message for the parent.
|
|
119
|
+
* optional out-message for the parent. The value lives in the parent Model:
|
|
120
|
+
* the view supplies the current value on the messages that need it, and value
|
|
121
|
+
* changes surface as `ChangedValue` rather than mutating this Model. */
|
|
118
122
|
export const update = (model, message) => M.value(message).pipe(withUpdateReturn, M.tagsExhaustive({
|
|
119
|
-
PressedThumb: () => M.value(model.dragState).pipe(withUpdateReturn, M.tag('Dragging', () => [model, [], Option.none()]), M.orElse(() => [
|
|
120
|
-
evo(model, {
|
|
121
|
-
dragState: () => Dragging({ originValue: model.value }),
|
|
122
|
-
}),
|
|
123
|
+
PressedThumb: ({ originValue }) => M.value(model.dragState).pipe(withUpdateReturn, M.tag('Dragging', () => [model, [], Option.none()]), M.orElse(() => [
|
|
124
|
+
evo(model, { dragState: () => Dragging({ originValue }) }),
|
|
123
125
|
[],
|
|
124
126
|
Option.none(),
|
|
125
127
|
])),
|
|
@@ -129,48 +131,45 @@ export const update = (model, message) => M.value(message).pipe(withUpdateReturn
|
|
|
129
131
|
// from the thumb's current position. Fine-grained sliders (e.g. step
|
|
130
132
|
// 0.05) see a visible jump without this guard, because the cursor sits
|
|
131
133
|
// off-center on a non-zero-width thumb.
|
|
132
|
-
PressedPointer: ({ value }) => M.value(model.dragState).pipe(withUpdateReturn, M.tag('Dragging', () => [model, [], Option.none()]), M.orElse(() => {
|
|
134
|
+
PressedPointer: ({ value, originValue }) => M.value(model.dragState).pipe(withUpdateReturn, M.tag('Dragging', () => [model, [], Option.none()]), M.orElse(() => {
|
|
133
135
|
const snapped = snapAndClamp(value, model.min, model.max, model.step);
|
|
134
|
-
const [modelWithValue, commands, maybeOutMessage] = withValue(model, snapped, []);
|
|
135
136
|
return [
|
|
136
|
-
evo(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
commands,
|
|
140
|
-
maybeOutMessage,
|
|
137
|
+
evo(model, { dragState: () => Dragging({ originValue }) }),
|
|
138
|
+
[],
|
|
139
|
+
changedValueOption(originValue, snapped),
|
|
141
140
|
];
|
|
142
141
|
})),
|
|
143
|
-
MovedDragPointer: ({ value }) => M.value(model.dragState).pipe(withUpdateReturn, M.tag('Dragging', () =>
|
|
142
|
+
MovedDragPointer: ({ value }) => M.value(model.dragState).pipe(withUpdateReturn, M.tag('Dragging', () => [
|
|
143
|
+
model,
|
|
144
|
+
[],
|
|
145
|
+
Option.some(ChangedValue({
|
|
146
|
+
value: snapAndClamp(value, model.min, model.max, model.step),
|
|
147
|
+
})),
|
|
148
|
+
]), M.orElse(() => [model, [], Option.none()])),
|
|
144
149
|
ReleasedDragPointer: () => M.value(model.dragState).pipe(withUpdateReturn, M.tag('Dragging', () => [
|
|
145
150
|
evo(model, { dragState: () => Idle() }),
|
|
146
151
|
[],
|
|
147
152
|
Option.none(),
|
|
148
153
|
]), M.orElse(() => [model, [], Option.none()])),
|
|
149
|
-
CancelledDrag: () => M.value(model.dragState).pipe(withUpdateReturn, M.tag('Dragging', ({ originValue }) =>
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
CancelledDrag: () => M.value(model.dragState).pipe(withUpdateReturn, M.tag('Dragging', ({ originValue }) => [
|
|
155
|
+
evo(model, { dragState: () => Idle() }),
|
|
156
|
+
[],
|
|
157
|
+
Option.some(ChangedValue({ value: originValue })),
|
|
158
|
+
]), M.orElse(() => [model, [], Option.none()])),
|
|
159
|
+
PressedKeyboardNavigation: ({ direction, value }) => [
|
|
160
|
+
model,
|
|
161
|
+
[],
|
|
162
|
+
changedValueOption(value, nextValueForDirection(value, model.min, model.max, model.step, direction)),
|
|
163
|
+
],
|
|
156
164
|
}));
|
|
157
|
-
/** Reflects an externally-driven range onto the slider.
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* structural range change cannot leave the value out of bounds. */
|
|
165
|
+
/** Reflects an externally-driven range onto the slider. Use this when min/max
|
|
166
|
+
* derive from external state (e.g. a bounded buffer whose first/last index
|
|
167
|
+
* shifts over time). The parent owns the value, so conform it to the new range
|
|
168
|
+
* in the same update with {@link snapAndClamp}. */
|
|
162
169
|
export const reflectRange = Function.dual(2, (model, range) => evo(model, {
|
|
163
170
|
min: () => range.min,
|
|
164
171
|
max: () => range.max,
|
|
165
|
-
value: current => snapAndClamp(current, range.min, range.max, model.step),
|
|
166
172
|
}));
|
|
167
|
-
/** Reflects an externally-driven value onto the slider, snapped and clamped
|
|
168
|
-
* into range; a no-op while the user is dragging, since drag state owns the
|
|
169
|
-
* value. Does not emit `ChangedValue`; use when the value is being driven by
|
|
170
|
-
* external state rather than user input. */
|
|
171
|
-
export const reflectValue = Function.dual(2, (model, value) => M.value(model.dragState).pipe(M.withReturnType(), M.tag('Dragging', () => model), M.orElse(() => evo(model, {
|
|
172
|
-
value: () => snapAndClamp(value, model.min, model.max, model.step),
|
|
173
|
-
}))));
|
|
174
173
|
// SUBSCRIPTION
|
|
175
174
|
const DragActivity = S.Literals(['Idle', 'Active']);
|
|
176
175
|
const dragActivityFromModel = (model) => M.value(model.dragState).pipe(M.withReturnType(), M.tag('Dragging', () => 'Active'), M.orElse(() => 'Idle'));
|
|
@@ -244,16 +243,17 @@ const percentString = (fraction) => `${Math.round(fraction * 10000) / 100}%`;
|
|
|
244
243
|
* end. Pointer drag is handled by the component's drag subscriptions. */
|
|
245
244
|
export const view = defineView((model, viewInputs) => {
|
|
246
245
|
const h = html();
|
|
247
|
-
const { formatValue, isDisabled = false, name, getTrackRoot = () => document, } = viewInputs;
|
|
248
|
-
const { id,
|
|
246
|
+
const { value, formatValue, isDisabled = false, name, getTrackRoot = () => document, } = viewInputs;
|
|
247
|
+
const { id, min, max } = model;
|
|
249
248
|
const isDragging = model.dragState._tag === 'Dragging';
|
|
250
|
-
const fraction = fractionOfValue(
|
|
251
|
-
const handleKeyDown = (key) => Option.map(keyToDirection(key), direction => PressedKeyboardNavigation({ direction }));
|
|
249
|
+
const fraction = fractionOfValue(value, min, max);
|
|
250
|
+
const handleKeyDown = (key) => Option.map(keyToDirection(key), direction => PressedKeyboardNavigation({ direction, value }));
|
|
252
251
|
const pointerAtClientX = (clientX) => Option.map(trackElement(id, getTrackRoot()), element => PressedPointer({
|
|
253
252
|
value: valueFromClientX(clientX, element, min, max),
|
|
253
|
+
originValue: value,
|
|
254
254
|
}));
|
|
255
255
|
const trackPointerHandler = (_pointerType, button, _screenX, _screenY, _timeStamp, clientX) => pipe(button, Option.liftPredicate(Equal.equals(LEFT_MOUSE_BUTTON)), Option.flatMap(() => pointerAtClientX(clientX)));
|
|
256
|
-
const thumbPointerHandler = (_pointerType, button) => pipe(button, Option.liftPredicate(Equal.equals(LEFT_MOUSE_BUTTON)), Option.map(() => PressedThumb()));
|
|
256
|
+
const thumbPointerHandler = (_pointerType, button) => pipe(button, Option.liftPredicate(Equal.equals(LEFT_MOUSE_BUTTON)), Option.map(() => PressedThumb({ originValue: value })));
|
|
257
257
|
const stateAttributes = [
|
|
258
258
|
...(isDragging ? [h.DataAttribute('dragging', '')] : []),
|
|
259
259
|
...(isDisabled ? [h.DataAttribute('disabled', '')] : []),
|
package/dist/slider/public.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { init, update, reflectRange,
|
|
1
|
+
export { init, update, reflectRange, view, subscriptions, subscriptionsForRoot, fractionOfValue, snapAndClamp, Model, Message, OutMessage, } from './index.js';
|
|
2
2
|
export type { InitConfig, ViewInputs, SliderAttributes, PressedThumb, PressedPointer, MovedDragPointer, ReleasedDragPointer, CancelledDrag, PressedKeyboardNavigation, } from './index.js';
|
|
3
3
|
//# sourceMappingURL=public.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/slider/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,MAAM,EACN,YAAY,EACZ,
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/slider/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,MAAM,EACN,YAAY,EACZ,IAAI,EACJ,aAAa,EACb,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,KAAK,EACL,OAAO,EACP,UAAU,GACX,MAAM,YAAY,CAAA;AAEnB,YAAY,EACV,UAAU,EACV,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,yBAAyB,GAC1B,MAAM,YAAY,CAAA"}
|
package/dist/slider/public.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { init, update, reflectRange,
|
|
1
|
+
export { init, update, reflectRange, view, subscriptions, subscriptionsForRoot, fractionOfValue, snapAndClamp, Model, Message, OutMessage, } from './index.js';
|
package/dist/switch/index.d.ts
CHANGED
|
@@ -1,99 +1,52 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { type Attribute, type Html } from 'foldkit/html';
|
|
2
|
+
/** Attribute groups the switch provides to the consumer's `toView` callback.
|
|
3
|
+
* Each group is a `ReadonlyArray<Attribute<Message>>` the consumer
|
|
4
|
+
* spreads into its own element attribute arrays. The `button` and `label`
|
|
5
|
+
* bundles carry the click and Space handlers that dispatch the configured
|
|
6
|
+
* `onToggle` Message. */
|
|
7
|
+
export type SwitchAttributes<Message> = Readonly<{
|
|
8
|
+
button: ReadonlyArray<Attribute<Message>>;
|
|
9
|
+
label: ReadonlyArray<Attribute<Message>>;
|
|
10
|
+
description: ReadonlyArray<Attribute<Message>>;
|
|
11
|
+
hiddenInput: ReadonlyArray<Attribute<Message>>;
|
|
9
12
|
}>;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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<{
|
|
13
|
+
/** Per-render view configuration for the stateless controlled {@link view}.
|
|
14
|
+
* Generic over `Message` (the message `onToggle` dispatches).
|
|
15
|
+
*
|
|
16
|
+
* - `isChecked`: the current checked state, read straight from the parent
|
|
17
|
+
* Model. `aria-checked` and the `data-checked` marker derive from it.
|
|
18
|
+
* - `onToggle`: dispatched with the new checked state when the user clicks
|
|
19
|
+
* the switch or its label, or presses Space. Handle it in the parent's
|
|
20
|
+
* `update` by storing the value.
|
|
21
|
+
* - `toView`: receives the {@link SwitchAttributes} and lays out the
|
|
22
|
+
* switch. */
|
|
23
|
+
export type ViewConfig<Message> = Readonly<{
|
|
44
24
|
id: string;
|
|
45
|
-
isChecked
|
|
46
|
-
|
|
47
|
-
|
|
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;
|
|
25
|
+
isChecked: boolean;
|
|
26
|
+
onToggle: (isChecked: boolean) => Message;
|
|
27
|
+
toView: (attributes: SwitchAttributes<Message>) => Html;
|
|
78
28
|
isDisabled?: boolean;
|
|
79
29
|
name?: string;
|
|
80
30
|
value?: string;
|
|
81
31
|
}>;
|
|
82
|
-
/** Renders an accessible switch
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
32
|
+
/** Renders an accessible switch as a stateless controlled component. The
|
|
33
|
+
* parent owns the checked state (`isChecked`) and receives the new state via
|
|
34
|
+
* `onToggle` when the user toggles it.
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* // In view:
|
|
38
|
+
* Switch.view<Message>({
|
|
39
|
+
* id: 'notifications',
|
|
40
|
+
* isChecked: model.notificationsEnabled,
|
|
41
|
+
* onToggle: isChecked => ToggledNotifications({ isChecked }),
|
|
42
|
+
* toView: attributes => ...,
|
|
43
|
+
* })
|
|
44
|
+
*
|
|
45
|
+
* // In update:
|
|
46
|
+
* ToggledNotifications: ({ isChecked }) => [
|
|
47
|
+
* evo(model, { notificationsEnabled: () => isChecked }),
|
|
48
|
+
* [],
|
|
49
|
+
* ],
|
|
50
|
+
* ``` */
|
|
51
|
+
export declare const view: <Message>(config: ViewConfig<Message>) => Html;
|
|
99
52
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/switch/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/switch/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,IAAI,EAAQ,MAAM,cAAc,CAAA;AAI9D;;;;0BAI0B;AAC1B,MAAM,MAAM,gBAAgB,CAAC,OAAO,IAAI,QAAQ,CAAC;IAC/C,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IACzC,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IACxC,WAAW,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;IAC9C,WAAW,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;CAC/C,CAAC,CAAA;AAEF;;;;;;;;;gBASgB;AAChB,MAAM,MAAM,UAAU,CAAC,OAAO,IAAI,QAAQ,CAAC;IACzC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,OAAO,CAAA;IAClB,QAAQ,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,OAAO,CAAA;IACzC,MAAM,EAAE,CAAC,UAAU,EAAE,gBAAgB,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;IACvD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAC,CAAA;AAKF;;;;;;;;;;;;;;;;;;UAkBU;AACV,eAAO,MAAM,IAAI,GAAI,OAAO,EAAE,QAAQ,UAAU,CAAC,OAAO,CAAC,KAAG,IA4D3D,CAAA"}
|
package/dist/switch/index.js
CHANGED
|
@@ -1,79 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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 }));
|
|
1
|
+
import { Match as M, Option } from 'effect';
|
|
2
|
+
import { html } from 'foldkit/html';
|
|
67
3
|
const labelId = (id) => `${id}-label`;
|
|
68
4
|
const descriptionId = (id) => `${id}-description`;
|
|
69
|
-
/** Renders an accessible switch
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
|
|
5
|
+
/** Renders an accessible switch as a stateless controlled component. The
|
|
6
|
+
* parent owns the checked state (`isChecked`) and receives the new state via
|
|
7
|
+
* `onToggle` when the user toggles it.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* // In view:
|
|
11
|
+
* Switch.view<Message>({
|
|
12
|
+
* id: 'notifications',
|
|
13
|
+
* isChecked: model.notificationsEnabled,
|
|
14
|
+
* onToggle: isChecked => ToggledNotifications({ isChecked }),
|
|
15
|
+
* toView: attributes => ...,
|
|
16
|
+
* })
|
|
17
|
+
*
|
|
18
|
+
* // In update:
|
|
19
|
+
* ToggledNotifications: ({ isChecked }) => [
|
|
20
|
+
* evo(model, { notificationsEnabled: () => isChecked }),
|
|
21
|
+
* [],
|
|
22
|
+
* ],
|
|
23
|
+
* ``` */
|
|
24
|
+
export const view = (config) => {
|
|
73
25
|
const h = html();
|
|
74
|
-
const { id, isChecked } =
|
|
75
|
-
const
|
|
76
|
-
const handleKeyUp = (key) => M.value(key).pipe(M.when(' ', () => Option.some(
|
|
26
|
+
const { id, isChecked, onToggle, toView, isDisabled = false, name, value: formValue = 'on', } = config;
|
|
27
|
+
const nextChecked = !isChecked;
|
|
28
|
+
const handleKeyUp = (key) => M.value(key).pipe(M.when(' ', () => Option.some(onToggle(nextChecked))), M.orElse(() => Option.none()));
|
|
77
29
|
const checkedAttributes = isChecked ? [h.DataAttribute('checked', '')] : [];
|
|
78
30
|
const disabledAttributes = isDisabled
|
|
79
31
|
? [h.AriaDisabled(true), h.DataAttribute('disabled', '')]
|
|
@@ -88,20 +40,23 @@ export const view = defineView((model, viewInputs) => {
|
|
|
88
40
|
...disabledAttributes,
|
|
89
41
|
...(isDisabled
|
|
90
42
|
? []
|
|
91
|
-
: [
|
|
43
|
+
: [
|
|
44
|
+
h.OnClick(onToggle(nextChecked)),
|
|
45
|
+
h.OnKeyUpPreventDefault(handleKeyUp),
|
|
46
|
+
]),
|
|
92
47
|
];
|
|
93
48
|
const labelAttributes = [
|
|
94
49
|
h.Id(labelId(id)),
|
|
95
|
-
...(isDisabled ? [] : [h.OnClick(
|
|
50
|
+
...(isDisabled ? [] : [h.OnClick(onToggle(nextChecked))]),
|
|
96
51
|
];
|
|
97
52
|
const descriptionAttributes = [h.Id(descriptionId(id))];
|
|
98
53
|
const hiddenInputAttributes = name
|
|
99
54
|
? [h.Type('hidden'), h.Name(name), h.Value(isChecked ? formValue : '')]
|
|
100
55
|
: [];
|
|
101
|
-
return
|
|
102
|
-
button:
|
|
103
|
-
label:
|
|
104
|
-
description:
|
|
105
|
-
hiddenInput:
|
|
56
|
+
return toView({
|
|
57
|
+
button: buttonAttributes,
|
|
58
|
+
label: labelAttributes,
|
|
59
|
+
description: descriptionAttributes,
|
|
60
|
+
hiddenInput: hiddenInputAttributes,
|
|
106
61
|
});
|
|
107
|
-
}
|
|
62
|
+
};
|
package/dist/switch/public.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export type {
|
|
1
|
+
export { view } from './index.js';
|
|
2
|
+
export type { ViewConfig, SwitchAttributes } from './index.js';
|
|
3
3
|
//# sourceMappingURL=public.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/switch/public.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/switch/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEjC,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA"}
|
package/dist/switch/public.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { view } from './index.js';
|