@fluixi-ui/date-picker 0.3.0-alpha.18 → 0.3.0-alpha.19

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.
@@ -1,236 +1,341 @@
1
- import { delegateEvents, createComponent, splitProps, createNativeElement, spread, mergeProps, insert, handleRef } from "@fluixi/dom";
2
- import { createSignal, createMemo, createEffect, createRenderEffect } from "@fluixi/reactive/signal";
1
+ import { createComponent, createNativeElement, delegateEvents, handleRef, insert, mergeProps, splitProps, spread } from "@fluixi/dom";
2
+ import { createEffect, createMemo, createRenderEffect, createSignal } from "@fluixi/reactive/signal";
3
3
  import { fxProps } from "@fluixi-ui/fx";
4
- import { useRef, Show } from "@fluixi/core";
5
- import { createSafeContext, createControllableSignal, createControllableBooleanSignal, createId, composeId, composeEventHandlers } from "@fluixi-ui/kit";
6
- import { Portal, createFloatingPosition, createDismissableLayer, createFocusScope } from "@fluixi-ui/primitives";
4
+ import { Show, useRef } from "@fluixi/core";
5
+ import { composeEventHandlers, composeId, createControllableBooleanSignal, createControllableSignal, createId, createSafeContext } from "@fluixi-ui/kit";
6
+ import { Portal, createDismissableLayer, createFloatingPosition, createFocusScope } from "@fluixi-ui/primitives";
7
7
  import { DateField, DateInput } from "@fluixi-ui/date-field";
8
- import { Calendar, CalendarHeader, CalendarPrevButton, CalendarHeading, CalendarNextButton, CalendarGrid } from "@fluixi-ui/calendar";
9
- delegateEvents(["change", "keydown", "click"]);
10
- const [DatePickerProvider, useDatePickerContext] = createSafeContext("DatePicker");
8
+ import { Calendar, CalendarGrid, CalendarHeader, CalendarHeading, CalendarNextButton, CalendarPrevButton } from "@fluixi-ui/calendar";
9
+ //#region src/date-picker.tsx
10
+ delegateEvents([
11
+ "change",
12
+ "keydown",
13
+ "click"
14
+ ]);
15
+ var [DatePickerProvider, useDatePickerContext] = createSafeContext("DatePicker");
16
+ /**
17
+ * A date picker: a segmented field paired with a popover calendar over one
18
+ * shared value. Typing in the field and picking in the calendar stay in sync;
19
+ * selecting a day closes the popover.
20
+ */
11
21
  function DatePicker(props) {
12
- const [value, setValue] = createControllableSignal({
13
- value: () => props.value ?? void 0,
14
- defaultValue: () => props.defaultValue ?? null,
15
- onChange: (v) => props.onChange?.(v)
16
- });
17
- const [isOpen, setOpen] = createControllableBooleanSignal({
18
- value: () => props.open,
19
- defaultValue: () => props.defaultOpen ?? false,
20
- onChange: (o) => props.onOpenChange?.(o)
21
- });
22
- const baseId = createId("date-picker");
23
- const [triggerRef, setTriggerRef] = createSignal(void 0);
24
- const ctx = {
25
- value: () => value() ?? null,
26
- setValue,
27
- isOpen,
28
- setOpen: (o) => setOpen(o),
29
- ids: { label: composeId(baseId, "label"), content: composeId(baseId, "content") },
30
- triggerRef,
31
- setTriggerRef,
32
- placement: props.placement ?? "bottom-start",
33
- offset: props.offset ?? 6,
34
- locale: () => props.locale,
35
- granularity: props.granularity,
36
- hourCycle: props.hourCycle,
37
- minValue: () => props.minValue,
38
- maxValue: () => props.maxValue,
39
- isDisabled: () => props.isDisabled ?? false,
40
- isReadOnly: () => props.isReadOnly ?? false,
41
- isDateUnavailable: props.isDateUnavailable,
42
- name: props.name
43
- };
44
- return createMemo(() => createComponent(DatePickerProvider, { get value() {
45
- return ctx;
46
- }, get children() {
47
- return props.children;
48
- } }));
22
+ const [value, setValue] = createControllableSignal({
23
+ value: () => props.value ?? void 0,
24
+ defaultValue: () => props.defaultValue ?? null,
25
+ onChange: (v) => props.onChange?.(v)
26
+ });
27
+ const [isOpen, setOpen] = createControllableBooleanSignal({
28
+ value: () => props.open,
29
+ defaultValue: () => props.defaultOpen ?? false,
30
+ onChange: (o) => props.onOpenChange?.(o)
31
+ });
32
+ const baseId = createId("date-picker");
33
+ const [triggerRef, setTriggerRef] = createSignal(void 0);
34
+ const ctx = {
35
+ value: () => value() ?? null,
36
+ setValue,
37
+ isOpen,
38
+ setOpen: (o) => setOpen(o),
39
+ ids: {
40
+ label: composeId(baseId, "label"),
41
+ content: composeId(baseId, "content")
42
+ },
43
+ triggerRef,
44
+ setTriggerRef,
45
+ placement: props.placement ?? "bottom-start",
46
+ offset: props.offset ?? 6,
47
+ locale: () => props.locale,
48
+ granularity: props.granularity,
49
+ hourCycle: props.hourCycle,
50
+ minValue: () => props.minValue,
51
+ maxValue: () => props.maxValue,
52
+ isDisabled: () => props.isDisabled ?? false,
53
+ isReadOnly: () => props.isReadOnly ?? false,
54
+ isDateUnavailable: props.isDateUnavailable,
55
+ name: props.name
56
+ };
57
+ return createMemo(() => createComponent(DatePickerProvider, {
58
+ get value() {
59
+ return ctx;
60
+ },
61
+ get children() {
62
+ return props.children;
63
+ }
64
+ }));
49
65
  }
66
+ /** Field label; linked to the segmented input. */
50
67
  function DatePickerLabel(props) {
51
- const ctx = useDatePickerContext();
52
- const [local, others] = splitProps(props, ["children", "fx", "style"]);
53
- return (() => {
54
- const _el$ = createNativeElement("span");
55
- spread({ element: _el$, props: mergeProps(fxProps(
56
- () => local.fx,
57
- () => local.style
58
- ), others, { id: () => ctx.ids.label, "data-date-picker-label": "" }) });
59
- insert(_el$, local.children);
60
- return _el$;
61
- })();
68
+ const ctx = useDatePickerContext();
69
+ const [local, others] = splitProps(props, [
70
+ "children",
71
+ "fx",
72
+ "style"
73
+ ]);
74
+ return (() => {
75
+ const _el$ = createNativeElement("span");
76
+ spread({
77
+ element: _el$,
78
+ props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
79
+ id: () => ctx.ids.label,
80
+ "data-date-picker-label": ""
81
+ })
82
+ });
83
+ insert(_el$, local.children, null);
84
+ return _el$;
85
+ })();
62
86
  }
87
+ /** The bordered control: the segmented date field plus the calendar trigger. */
63
88
  function DatePickerField(props) {
64
- const ctx = useDatePickerContext();
65
- const [local, others] = splitProps(props, ["ref", "children", "fx", "style"]);
66
- const setRef = (node) => {
67
- ctx.setTriggerRef(node);
68
- handleRef(local.ref, node);
69
- };
70
- return createMemo(() => createComponent(DateField, { get value() {
71
- return ctx.value;
72
- }, get onChange() {
73
- return ctx.setValue;
74
- }, get locale() {
75
- return ctx.locale;
76
- }, get granularity() {
77
- return ctx.granularity;
78
- }, get hourCycle() {
79
- return ctx.hourCycle;
80
- }, get minValue() {
81
- return ctx.minValue;
82
- }, get maxValue() {
83
- return ctx.maxValue;
84
- }, get isDisabled() {
85
- return ctx.isDisabled;
86
- }, get isReadOnly() {
87
- return ctx.isReadOnly;
88
- }, get name() {
89
- return ctx.name;
90
- }, get "data-date-picker"() {
91
- return "";
92
- }, get children() {
93
- return (() => {
94
- const _el$ = createNativeElement("div");
95
- spread({ element: _el$, props: mergeProps(fxProps(
96
- () => local.fx,
97
- () => local.style
98
- ), others, { ref: setRef, "data-date-picker-control": "" }) });
99
- insert(_el$, createMemo(() => createComponent(DateInput, { get "aria-labelledby"() {
100
- return ctx.ids.label;
101
- }, get "data-date-picker-input"() {
102
- return "";
103
- } })), null);
104
- insert(_el$, local.children ?? createMemo(() => createComponent(DatePickerTrigger, {})));
105
- return _el$;
106
- })();
107
- } }));
89
+ const ctx = useDatePickerContext();
90
+ const [local, others] = splitProps(props, [
91
+ "ref",
92
+ "children",
93
+ "fx",
94
+ "style"
95
+ ]);
96
+ const setRef = (node) => {
97
+ ctx.setTriggerRef(node);
98
+ handleRef(local.ref, node);
99
+ };
100
+ return createMemo(() => createComponent(DateField, {
101
+ get value() {
102
+ return ctx.value;
103
+ },
104
+ get onChange() {
105
+ return ctx.setValue;
106
+ },
107
+ get locale() {
108
+ return ctx.locale;
109
+ },
110
+ get granularity() {
111
+ return ctx.granularity;
112
+ },
113
+ get hourCycle() {
114
+ return ctx.hourCycle;
115
+ },
116
+ get minValue() {
117
+ return ctx.minValue;
118
+ },
119
+ get maxValue() {
120
+ return ctx.maxValue;
121
+ },
122
+ get isDisabled() {
123
+ return ctx.isDisabled;
124
+ },
125
+ get isReadOnly() {
126
+ return ctx.isReadOnly;
127
+ },
128
+ get name() {
129
+ return ctx.name;
130
+ },
131
+ get "data-date-picker"() {
132
+ return "";
133
+ },
134
+ get children() {
135
+ return (() => {
136
+ const _el$ = createNativeElement("div");
137
+ spread({
138
+ element: _el$,
139
+ props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
140
+ ref: setRef,
141
+ "data-date-picker-control": ""
142
+ })
143
+ });
144
+ insert(_el$, createMemo(() => createComponent(DateInput, {
145
+ get "aria-labelledby"() {
146
+ return ctx.ids.label;
147
+ },
148
+ get "data-date-picker-input"() {
149
+ return "";
150
+ }
151
+ })), null);
152
+ insert(_el$, local.children ?? createMemo(() => createComponent(DatePickerTrigger, {})), null);
153
+ return _el$;
154
+ })();
155
+ }
156
+ }));
108
157
  }
158
+ /** Toggles the calendar popover. */
109
159
  function DatePickerTrigger(props) {
110
- const ctx = useDatePickerContext();
111
- const [local, others] = splitProps(props, ["onClick", "children", "fx", "style"]);
112
- return (() => {
113
- const _el$ = createNativeElement("button");
114
- spread({ element: _el$, props: mergeProps(fxProps(
115
- () => local.fx,
116
- () => local.style
117
- ), others, { type: "button", "aria-haspopup": "dialog", "aria-expanded": () => ctx.isOpen() ? "true" : "false", "aria-controls": () => ctx.ids.content, "aria-label": "Open calendar", disabled: () => ctx.isDisabled(), "data-date-picker-trigger": "", onClick: composeEventHandlers([
118
- local.onClick,
119
- () => {
120
- if (ctx.isDisabled() || ctx.isReadOnly()) return;
121
- ctx.setOpen(!ctx.isOpen());
122
- }
123
- ]) }) });
124
- insert(_el$, local.children ?? "📅");
125
- return _el$;
126
- })();
160
+ const ctx = useDatePickerContext();
161
+ const [local, others] = splitProps(props, [
162
+ "onClick",
163
+ "children",
164
+ "fx",
165
+ "style"
166
+ ]);
167
+ return (() => {
168
+ const _el$ = createNativeElement("button");
169
+ spread({
170
+ element: _el$,
171
+ props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
172
+ type: "button",
173
+ "aria-haspopup": "dialog",
174
+ "aria-expanded": () => ctx.isOpen() ? "true" : "false",
175
+ "aria-controls": () => ctx.ids.content,
176
+ "aria-label": "Open calendar",
177
+ disabled: () => ctx.isDisabled(),
178
+ "data-date-picker-trigger": "",
179
+ onClick: composeEventHandlers([local.onClick, () => {
180
+ if (ctx.isDisabled() || ctx.isReadOnly()) return;
181
+ ctx.setOpen(!ctx.isOpen());
182
+ }])
183
+ })
184
+ });
185
+ insert(_el$, local.children ?? "📅", null);
186
+ return _el$;
187
+ })();
127
188
  }
189
+ /**
190
+ * The floating calendar panel. Renders a default calendar bound to the shared
191
+ * value (override by passing children); selecting a day closes the popover.
192
+ */
128
193
  function DatePickerContent(props) {
129
- const ctx = useDatePickerContext();
130
- const [local, others] = splitProps(props, ["ref", "children", "style", "fx", "style"]);
131
- const [element, setElement] = createSignal(void 0);
132
- const portalRef = useRef(null);
133
- createEffect(() => {
134
- if (!ctx.isOpen() && portalRef.current) portalRef.current.remove();
135
- });
136
- return createMemo(() => createComponent(Show, { get when() {
137
- return ctx.isOpen();
138
- }, get children() {
139
- return createMemo(() => createComponent(Portal, { get ref() {
140
- return portalRef;
141
- }, get children() {
142
- return createMemo(() => createComponent(FloatingPanel, mergeProps(others, { get setElement() {
143
- return setElement;
144
- }, get element() {
145
- return element;
146
- }, get forwardRef() {
147
- return local.ref;
148
- }, get fx() {
149
- return local.fx;
150
- }, get style() {
151
- return local.style;
152
- }, get children() {
153
- return local.children ?? createMemo(() => createComponent(DatePickerCalendar, {}));
154
- } })));
155
- } }));
156
- } }));
194
+ const ctx = useDatePickerContext();
195
+ const [local, others] = splitProps(props, [
196
+ "ref",
197
+ "children",
198
+ "style",
199
+ "fx",
200
+ "style"
201
+ ]);
202
+ const [element, setElement] = createSignal(void 0);
203
+ const portalRef = useRef(null);
204
+ createEffect(() => {
205
+ if (!ctx.isOpen() && portalRef.current) portalRef.current.remove();
206
+ });
207
+ return createMemo(() => createComponent(Show, {
208
+ get when() {
209
+ return ctx.isOpen();
210
+ },
211
+ get children() {
212
+ return createMemo(() => createComponent(Portal, {
213
+ get ref() {
214
+ return portalRef;
215
+ },
216
+ get children() {
217
+ return createMemo(() => createComponent(FloatingPanel, mergeProps(others, {
218
+ get setElement() {
219
+ return setElement;
220
+ },
221
+ get element() {
222
+ return element;
223
+ },
224
+ get forwardRef() {
225
+ return local.ref;
226
+ },
227
+ get fx() {
228
+ return local.fx;
229
+ },
230
+ get style() {
231
+ return local.style;
232
+ },
233
+ get children() {
234
+ return local.children ?? createMemo(() => createComponent(DatePickerCalendar, {}));
235
+ }
236
+ })));
237
+ }
238
+ }));
239
+ }
240
+ }));
157
241
  }
242
+ /** Inner panel — split out so the floating hooks run inside the portal. */
158
243
  function FloatingPanel(props) {
159
- const ctx = useDatePickerContext();
160
- const position = createFloatingPosition(ctx.triggerRef, props.element, {
161
- placement: ctx.placement,
162
- offset: ctx.offset
163
- });
164
- createDismissableLayer(props.element, {
165
- shouldExcludeTarget: (target) => {
166
- const trigger = ctx.triggerRef();
167
- return !!trigger && (trigger === target || trigger.contains(target));
168
- },
169
- onDismiss: () => ctx.setOpen(false)
170
- });
171
- createFocusScope(props.element, { trap: false, autoFocus: true, restoreFocus: true });
172
- createRenderEffect(() => {
173
- const el = props.element();
174
- if (!el) return;
175
- el.style.position = "absolute";
176
- el.style.left = `${Math.round(position.x())}px`;
177
- el.style.top = `${Math.round(position.y())}px`;
178
- });
179
- const setRef = (node) => {
180
- props.setElement(node);
181
- handleRef(props.forwardRef, node);
182
- };
183
- return (() => {
184
- const _el$ = createNativeElement("div");
185
- spread({ element: _el$, props: mergeProps(fxProps(
186
- () => props.fx,
187
- () => props.style
188
- ), { ref: setRef, role: "dialog", id: () => ctx.ids.content, "aria-label": "Calendar", "data-date-picker-content": "", "data-placement": () => position.placement(), tabindex: -1, onKeyDown: (e) => {
189
- if (e.key === "Escape") {
190
- e.stopPropagation();
191
- ctx.setOpen(false);
192
- ctx.triggerRef()?.querySelector("[data-date-picker-trigger]")?.focus();
193
- }
194
- } }) });
195
- insert(_el$, props.children);
196
- return _el$;
197
- })();
244
+ const ctx = useDatePickerContext();
245
+ const position = createFloatingPosition(ctx.triggerRef, props.element, {
246
+ placement: ctx.placement,
247
+ offset: ctx.offset
248
+ });
249
+ createDismissableLayer(props.element, {
250
+ shouldExcludeTarget: (target) => {
251
+ const trigger = ctx.triggerRef();
252
+ return !!trigger && (trigger === target || trigger.contains(target));
253
+ },
254
+ onDismiss: () => ctx.setOpen(false)
255
+ });
256
+ createFocusScope(props.element, {
257
+ trap: false,
258
+ autoFocus: true,
259
+ restoreFocus: true
260
+ });
261
+ createRenderEffect(() => {
262
+ const el = props.element();
263
+ if (!el) return;
264
+ el.style.position = "absolute";
265
+ el.style.left = `${Math.round(position.x())}px`;
266
+ el.style.top = `${Math.round(position.y())}px`;
267
+ });
268
+ const setRef = (node) => {
269
+ props.setElement(node);
270
+ handleRef(props.forwardRef, node);
271
+ };
272
+ return (() => {
273
+ const _el$ = createNativeElement("div");
274
+ spread({
275
+ element: _el$,
276
+ props: mergeProps(fxProps(() => props.fx, () => props.style), {
277
+ ref: setRef,
278
+ role: "dialog",
279
+ id: () => ctx.ids.content,
280
+ "aria-label": "Calendar",
281
+ "data-date-picker-content": "",
282
+ "data-placement": () => position.placement(),
283
+ tabindex: -1,
284
+ onKeyDown: (e) => {
285
+ if (e.key === "Escape") {
286
+ e.stopPropagation();
287
+ ctx.setOpen(false);
288
+ ctx.triggerRef()?.querySelector("[data-date-picker-trigger]")?.focus();
289
+ }
290
+ }
291
+ })
292
+ });
293
+ insert(_el$, props.children, null);
294
+ return _el$;
295
+ })();
198
296
  }
297
+ /** Default calendar inside the popover; closes on selection. */
199
298
  function DatePickerCalendar() {
200
- const ctx = useDatePickerContext();
201
- return createMemo(() => createComponent(Calendar, { get value() {
202
- return ctx.value;
203
- }, get onChange() {
204
- return (v) => {
205
- ctx.setValue(v);
206
- ctx.setOpen(false);
207
- };
208
- }, get locale() {
209
- return ctx.locale;
210
- }, get minValue() {
211
- return ctx.minValue;
212
- }, get maxValue() {
213
- return ctx.maxValue;
214
- }, get isDateUnavailable() {
215
- return ctx.isDateUnavailable;
216
- }, get "data-date-picker-calendar"() {
217
- return "";
218
- }, get children() {
219
- return [createMemo(() => createComponent(CalendarHeader, { get children() {
220
- return [createMemo(() => createComponent(CalendarPrevButton, { get children() {
221
- return "‹";
222
- } })), createMemo(() => createComponent(CalendarHeading, {})), createMemo(() => createComponent(CalendarNextButton, { get children() {
223
- return "›";
224
- } }))];
225
- } })), createMemo(() => createComponent(CalendarGrid, {}))];
226
- } }));
299
+ const ctx = useDatePickerContext();
300
+ return createMemo(() => createComponent(Calendar, {
301
+ get value() {
302
+ return ctx.value;
303
+ },
304
+ get onChange() {
305
+ return (v) => {
306
+ ctx.setValue(v);
307
+ ctx.setOpen(false);
308
+ };
309
+ },
310
+ get locale() {
311
+ return ctx.locale;
312
+ },
313
+ get minValue() {
314
+ return ctx.minValue;
315
+ },
316
+ get maxValue() {
317
+ return ctx.maxValue;
318
+ },
319
+ get isDateUnavailable() {
320
+ return ctx.isDateUnavailable;
321
+ },
322
+ get "data-date-picker-calendar"() {
323
+ return "";
324
+ },
325
+ get children() {
326
+ return [createMemo(() => createComponent(CalendarHeader, { get children() {
327
+ return [
328
+ createMemo(() => createComponent(CalendarPrevButton, { get children() {
329
+ return "‹";
330
+ } })),
331
+ createMemo(() => createComponent(CalendarHeading, {})),
332
+ createMemo(() => createComponent(CalendarNextButton, { get children() {
333
+ return "›";
334
+ } }))
335
+ ];
336
+ } })), createMemo(() => createComponent(CalendarGrid, {}))];
337
+ }
338
+ }));
227
339
  }
228
- export {
229
- DatePicker,
230
- DatePickerCalendar,
231
- DatePickerContent,
232
- DatePickerField,
233
- DatePickerLabel,
234
- DatePickerTrigger,
235
- useDatePickerContext
236
- };
340
+ //#endregion
341
+ export { DatePicker, DatePickerCalendar, DatePickerContent, DatePickerField, DatePickerLabel, DatePickerTrigger, useDatePickerContext };