@radix-ng/primitives 0.34.0 → 0.36.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/calendar/src/calendar-root.directive.d.ts +3 -3
- package/core/index.d.ts +3 -0
- package/core/src/accessor/provide-value-accessor.d.ts +1 -1
- package/core/src/clamp.d.ts +38 -0
- package/core/src/date-time/index.d.ts +3 -0
- package/core/src/date-time/parser.d.ts +37 -0
- package/core/src/date-time/parts.d.ts +12 -0
- package/core/src/date-time/segment.d.ts +4 -0
- package/core/src/date-time/types.d.ts +18 -1
- package/core/src/date-time/useDateField.d.ts +141 -0
- package/core/src/date-time/utils.d.ts +3 -0
- package/core/src/getActiveElement.d.ts +1 -0
- package/core/src/provide-token.d.ts +22 -0
- package/date-field/README.md +1 -0
- package/date-field/index.d.ts +11 -0
- package/date-field/src/date-field-context.token.d.ts +18 -0
- package/date-field/src/date-field-input.directive.d.ts +53 -0
- package/date-field/src/date-field-root.directive.d.ts +126 -0
- package/dialog/src/dialog-ref.d.ts +3 -0
- package/dialog/src/dialog.config.d.ts +1 -0
- package/fesm2022/radix-ng-primitives-calendar.mjs +6 -15
- package/fesm2022/radix-ng-primitives-calendar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-checkbox.mjs +3 -4
- package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-core.mjs +1097 -8
- package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-date-field.mjs +334 -0
- package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-dialog.mjs +54 -4
- package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs +0 -2
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-number-field.mjs +502 -0
- package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-progress.mjs +3 -3
- package/fesm2022/radix-ng-primitives-progress.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-radio.mjs +7 -7
- package/fesm2022/radix-ng-primitives-radio.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-switch.mjs +7 -15
- package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-tabs.mjs +3 -6
- package/fesm2022/radix-ng-primitives-tabs.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toggle-group.mjs +8 -10
- package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toggle.mjs +5 -12
- package/fesm2022/radix-ng-primitives-toggle.mjs.map +1 -1
- package/hover-card/src/hover-card-root.directive.d.ts +4 -4
- package/number-field/README.md +1 -0
- package/number-field/index.d.ts +17 -0
- package/number-field/src/number-field-context.token.d.ts +24 -0
- package/number-field/src/number-field-decrement.directive.d.ts +23 -0
- package/number-field/src/number-field-increment.directive.d.ts +23 -0
- package/number-field/src/number-field-input.directive.d.ts +22 -0
- package/number-field/src/number-field-root.directive.d.ts +86 -0
- package/number-field/src/types.d.ts +1 -0
- package/number-field/src/utils.d.ts +18 -0
- package/package.json +9 -1
- package/popover/src/popover-root.directive.d.ts +4 -4
- package/switch/src/switch-root.directive.d.ts +0 -1
- package/toggle/src/toggle.directive.d.ts +0 -1
- package/tooltip/src/tooltip-root.directive.d.ts +4 -4
@@ -1,6 +1,6 @@
|
|
1
|
-
import { NG_VALUE_ACCESSOR, NgControl, FormControlDirective, FormControlName, NgModel } from '@angular/forms';
|
2
1
|
import * as i0 from '@angular/core';
|
3
|
-
import { inject, ElementRef, NgZone, booleanAttribute, Input, Directive, APP_ID, Injectable, InjectionToken, effect, untracked } from '@angular/core';
|
2
|
+
import { forwardRef, inject, ElementRef, NgZone, booleanAttribute, Input, Directive, APP_ID, Injectable, InjectionToken, computed, effect, untracked } from '@angular/core';
|
3
|
+
import { NG_VALUE_ACCESSOR, NgControl, FormControlDirective, FormControlName, NgModel } from '@angular/forms';
|
4
4
|
import { DOCUMENT } from '@angular/common';
|
5
5
|
import { Platform } from '@angular/cdk/platform';
|
6
6
|
import { getLocalTimeZone, CalendarDateTime, ZonedDateTime, getDayOfWeek, DateFormatter, createCalendar, toCalendar, CalendarDate, startOfMonth, endOfMonth, today } from '@internationalized/date';
|
@@ -18,7 +18,7 @@ import { getLocalTimeZone, CalendarDateTime, ZonedDateTime, getDayOfWeek, DateFo
|
|
18
18
|
function provideValueAccessor(type) {
|
19
19
|
return {
|
20
20
|
provide: NG_VALUE_ACCESSOR,
|
21
|
-
useExisting: type,
|
21
|
+
useExisting: forwardRef(() => type),
|
22
22
|
multi: true
|
23
23
|
};
|
24
24
|
}
|
@@ -101,6 +101,76 @@ function getRequestAnimationFrame() {
|
|
101
101
|
// Get the requestAnimationFrame function or its polyfill.
|
102
102
|
const reqAnimationFrame = getRequestAnimationFrame();
|
103
103
|
|
104
|
+
/**
|
105
|
+
* The `clamp` function restricts a number within a specified range by returning the value itself if it
|
106
|
+
* falls within the range, or the closest boundary value if it exceeds the range.
|
107
|
+
* @param {number} value - The `value` parameter represents the number that you want to clamp within
|
108
|
+
* the specified range defined by `min` and `max` values.
|
109
|
+
* @param {number} min - If the `value` parameter is less than the `min` value, the
|
110
|
+
* function will return the `min` value.
|
111
|
+
* @param {number} max - If the `value` parameter is greater than the `max` value,
|
112
|
+
* the function will return `max`.
|
113
|
+
* @returns The `clamp` function returns the value of `value` constrained within the range defined by
|
114
|
+
* `min` and `max`.
|
115
|
+
*/
|
116
|
+
function clamp(value, min = Number.NEGATIVE_INFINITY, max = Number.POSITIVE_INFINITY) {
|
117
|
+
return Math.min(max, Math.max(min, value));
|
118
|
+
}
|
119
|
+
/**
|
120
|
+
* The function `roundToStepPrecision` rounds a number to a specified precision step.
|
121
|
+
* @param {number} value - The `value` parameter is the number that you want to round to a specific
|
122
|
+
* precision based on the `step` parameter.
|
123
|
+
* @param {number} step - The `step` parameter in the `roundToStepPrecision` function represents the
|
124
|
+
* interval at which you want to round the `value`. For example, if `step` is 0.5, the `value` will be
|
125
|
+
* rounded to the nearest half.
|
126
|
+
* @returns the `roundedValue` after rounding it to the precision specified by the `step`.
|
127
|
+
*/
|
128
|
+
function roundToStepPrecision(value, step) {
|
129
|
+
let roundedValue = value;
|
130
|
+
const stepString = step.toString();
|
131
|
+
const pointIndex = stepString.indexOf('.');
|
132
|
+
const precision = pointIndex >= 0 ? stepString.length - pointIndex : 0;
|
133
|
+
if (precision > 0) {
|
134
|
+
const pow = 10 ** precision;
|
135
|
+
roundedValue = Math.round(roundedValue * pow) / pow;
|
136
|
+
}
|
137
|
+
return roundedValue;
|
138
|
+
}
|
139
|
+
/**
|
140
|
+
* The function `snapValueToStep` snaps a given value to the nearest step within a specified range.
|
141
|
+
* @param {number} value - The `value` parameter represents the number that you want to snap to the
|
142
|
+
* nearest step value.
|
143
|
+
* @param {number | undefined} min - The `min` parameter represents the minimum value that the `value`
|
144
|
+
* should be snapped to. If `value` is less than `min`, it will be snapped to `min`. If `min` is not
|
145
|
+
* provided (undefined), then the snapping will not consider a minimum value.
|
146
|
+
* @param {number | undefined} max - The `max` parameter represents the maximum value that the `value`
|
147
|
+
* should be snapped to. It ensures that the snapped value does not exceed this maximum value.
|
148
|
+
* @param {number} step - The `step` parameter in the `snapValueToStep` function represents the
|
149
|
+
* interval at which the `value` should be snapped to. It determines the granularity of the snapping
|
150
|
+
* operation. For example, if `step` is 5, the `value` will be snapped to the nearest multiple of
|
151
|
+
* @returns a number that has been snapped to the nearest step value within the specified range of minimum and maximum values.
|
152
|
+
*/
|
153
|
+
function snapValueToStep(value, min, max, step) {
|
154
|
+
min = Number(min);
|
155
|
+
max = Number(max);
|
156
|
+
const remainder = (value - (Number.isNaN(min) ? 0 : min)) % step;
|
157
|
+
let snappedValue = roundToStepPrecision(Math.abs(remainder) * 2 >= step
|
158
|
+
? value + Math.sign(remainder) * (step - Math.abs(remainder))
|
159
|
+
: value - remainder, step);
|
160
|
+
if (!Number.isNaN(min)) {
|
161
|
+
if (snappedValue < min)
|
162
|
+
snappedValue = min;
|
163
|
+
else if (!Number.isNaN(max) && snappedValue > max)
|
164
|
+
snappedValue = min + Math.floor(roundToStepPrecision((max - min) / step, step)) * step;
|
165
|
+
}
|
166
|
+
else if (!Number.isNaN(max) && snappedValue > max) {
|
167
|
+
snappedValue = Math.floor(roundToStepPrecision(max / step, step)) * step;
|
168
|
+
}
|
169
|
+
// correct floating point behavior by rounding to step precision
|
170
|
+
snappedValue = roundToStepPrecision(snappedValue, step);
|
171
|
+
return snappedValue;
|
172
|
+
}
|
173
|
+
|
104
174
|
function injectDocument() {
|
105
175
|
return inject(DOCUMENT);
|
106
176
|
}
|
@@ -124,6 +194,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
|
|
124
194
|
}]
|
125
195
|
}] });
|
126
196
|
|
197
|
+
function getActiveElement() {
|
198
|
+
let activeElement = document.activeElement;
|
199
|
+
if (activeElement == null) {
|
200
|
+
return null;
|
201
|
+
}
|
202
|
+
while (activeElement != null &&
|
203
|
+
activeElement.shadowRoot != null &&
|
204
|
+
activeElement.shadowRoot.activeElement != null) {
|
205
|
+
activeElement = activeElement.shadowRoot.activeElement;
|
206
|
+
}
|
207
|
+
return activeElement;
|
208
|
+
}
|
209
|
+
|
127
210
|
/**
|
128
211
|
* @license
|
129
212
|
* Copyright Google LLC All Rights Reserved.
|
@@ -239,6 +322,33 @@ const j = 'j';
|
|
239
322
|
const k = 'k';
|
240
323
|
const SPACE_CODE = 'Space';
|
241
324
|
|
325
|
+
/**
|
326
|
+
* Creates an Angular provider that binds the given token to the existing instance
|
327
|
+
* of the specified class. This is especially useful when you want multiple
|
328
|
+
* tokens (or interfaces) to resolve to the same directive/component instance.
|
329
|
+
*
|
330
|
+
* @template T - The type associated with the injection token.
|
331
|
+
* @param token - The InjectionToken or abstract type you want to provide.
|
332
|
+
* @param type - The class type whose existing instance will be used for this token.
|
333
|
+
* @returns A Provider configuration object for Angular's DI system.
|
334
|
+
*
|
335
|
+
* @example
|
336
|
+
*
|
337
|
+
* @Directive({
|
338
|
+
* providers: [
|
339
|
+
* provideToken(RdxToggleGroupToken, RdxToggleGroupDirective),
|
340
|
+
* provideValueAccessor(RdxToggleGroupDirective)
|
341
|
+
* ]
|
342
|
+
* })
|
343
|
+
* export class RdxToggleGroupDirective {}
|
344
|
+
*/
|
345
|
+
function provideToken(token, type) {
|
346
|
+
return {
|
347
|
+
provide: token,
|
348
|
+
useExisting: forwardRef(() => type)
|
349
|
+
};
|
350
|
+
}
|
351
|
+
|
242
352
|
const WINDOW = new InjectionToken('An abstraction over global window object', {
|
243
353
|
factory: () => {
|
244
354
|
const { defaultView } = injectDocument();
|
@@ -606,6 +716,32 @@ function createFormatter(initialLocale) {
|
|
606
716
|
};
|
607
717
|
}
|
608
718
|
|
719
|
+
/*
|
720
|
+
* Implementation ported from https://github.com/melt-ui/melt-ui/blob/develop/src/lib/builders/date-field/_internal/helpers.ts
|
721
|
+
*/
|
722
|
+
const DATE_SEGMENT_PARTS = ['day', 'month', 'year'];
|
723
|
+
const TIME_SEGMENT_PARTS = ['hour', 'minute', 'second', 'dayPeriod'];
|
724
|
+
const NON_EDITABLE_SEGMENT_PARTS = ['literal', 'timeZoneName'];
|
725
|
+
const EDITABLE_SEGMENT_PARTS = [...DATE_SEGMENT_PARTS, ...TIME_SEGMENT_PARTS];
|
726
|
+
const EDITABLE_TIME_SEGMENT_PARTS = [...TIME_SEGMENT_PARTS];
|
727
|
+
const ALL_SEGMENT_PARTS = [
|
728
|
+
...EDITABLE_SEGMENT_PARTS,
|
729
|
+
...NON_EDITABLE_SEGMENT_PARTS
|
730
|
+
];
|
731
|
+
const ALL_EXCEPT_LITERAL_PARTS = ALL_SEGMENT_PARTS.filter((part) => part !== 'literal');
|
732
|
+
function isDateSegmentPart(part) {
|
733
|
+
return DATE_SEGMENT_PARTS.includes(part);
|
734
|
+
}
|
735
|
+
function isTimeSegmentPart(part) {
|
736
|
+
return TIME_SEGMENT_PARTS.includes(part);
|
737
|
+
}
|
738
|
+
function isSegmentPart(part) {
|
739
|
+
return EDITABLE_SEGMENT_PARTS.includes(part);
|
740
|
+
}
|
741
|
+
function isAnySegmentPart(part) {
|
742
|
+
return ALL_SEGMENT_PARTS.includes(part);
|
743
|
+
}
|
744
|
+
|
609
745
|
/*
|
610
746
|
* Implementation ported from from from https://github.com/melt-ui/melt-ui/blob/develop/src/lib/internal/helpers/date/placeholders.ts
|
611
747
|
*/
|
@@ -739,10 +875,37 @@ function getLocaleLanguage(locale) {
|
|
739
875
|
return locale.split('-')[0];
|
740
876
|
}
|
741
877
|
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
878
|
+
function getOptsByGranularity(granularity, hourCycle, isTimeValue = false) {
|
879
|
+
const opts = {
|
880
|
+
year: 'numeric',
|
881
|
+
month: '2-digit',
|
882
|
+
day: '2-digit',
|
883
|
+
hour: '2-digit',
|
884
|
+
minute: '2-digit',
|
885
|
+
second: '2-digit',
|
886
|
+
timeZoneName: 'short',
|
887
|
+
hourCycle: hourCycle === 24 ? 'h23' : undefined,
|
888
|
+
hour12: hourCycle === 24 ? false : undefined
|
889
|
+
};
|
890
|
+
if (isTimeValue) {
|
891
|
+
delete opts.year;
|
892
|
+
delete opts.month;
|
893
|
+
delete opts.day;
|
894
|
+
}
|
895
|
+
if (granularity === 'day') {
|
896
|
+
delete opts.second;
|
897
|
+
delete opts.hour;
|
898
|
+
delete opts.minute;
|
899
|
+
delete opts.timeZoneName;
|
900
|
+
}
|
901
|
+
if (granularity === 'hour') {
|
902
|
+
delete opts.minute;
|
903
|
+
delete opts.second;
|
904
|
+
}
|
905
|
+
if (granularity === 'minute')
|
906
|
+
delete opts.second;
|
907
|
+
return opts;
|
908
|
+
}
|
746
909
|
function handleCalendarInitialFocus(calendar) {
|
747
910
|
const selectedDay = calendar.querySelector('[data-selected]');
|
748
911
|
if (selectedDay)
|
@@ -755,6 +918,932 @@ function handleCalendarInitialFocus(calendar) {
|
|
755
918
|
return firstDay.focus();
|
756
919
|
}
|
757
920
|
|
921
|
+
const calendarDateTimeGranularities = ['hour', 'minute', 'second'];
|
922
|
+
function syncTimeSegmentValues(props) {
|
923
|
+
return Object.fromEntries(TIME_SEGMENT_PARTS.map((part) => {
|
924
|
+
if (part === 'dayPeriod')
|
925
|
+
return [part, props.formatter.dayPeriod(toDate(props.value))];
|
926
|
+
return [part, props.value[part]];
|
927
|
+
}));
|
928
|
+
}
|
929
|
+
function initializeSegmentValues(granularity) {
|
930
|
+
const initialParts = EDITABLE_SEGMENT_PARTS.map((part) => {
|
931
|
+
if (part === 'dayPeriod')
|
932
|
+
return [part, 'AM'];
|
933
|
+
return [part, null];
|
934
|
+
}).filter(([key]) => {
|
935
|
+
if (key === 'literal' || key === null)
|
936
|
+
return false;
|
937
|
+
if (granularity === 'minute' && key === 'second')
|
938
|
+
return false;
|
939
|
+
if (granularity === 'hour' && (key === 'second' || key === 'minute'))
|
940
|
+
return false;
|
941
|
+
if (granularity === 'day')
|
942
|
+
return !calendarDateTimeGranularities.includes(key) && key !== 'dayPeriod';
|
943
|
+
else
|
944
|
+
return true;
|
945
|
+
});
|
946
|
+
return Object.fromEntries(initialParts);
|
947
|
+
}
|
948
|
+
function syncSegmentValues(props) {
|
949
|
+
const { formatter } = props;
|
950
|
+
const dateValues = DATE_SEGMENT_PARTS.map((part) => {
|
951
|
+
return [part, props.value[part]];
|
952
|
+
});
|
953
|
+
if ('hour' in props.value) {
|
954
|
+
const timeValues = syncTimeSegmentValues({ value: props.value, formatter });
|
955
|
+
return { ...Object.fromEntries(dateValues), ...timeValues };
|
956
|
+
}
|
957
|
+
return Object.fromEntries(dateValues);
|
958
|
+
}
|
959
|
+
function createContentObj(props) {
|
960
|
+
const { segmentValues, formatter, locale } = props;
|
961
|
+
function getPartContent(part) {
|
962
|
+
if ('hour' in segmentValues) {
|
963
|
+
const value = segmentValues[part];
|
964
|
+
if (value !== null) {
|
965
|
+
/**
|
966
|
+
* Edge case for when the month field is filled and the day field snaps to the maximum value of the value of the placeholder month
|
967
|
+
*/
|
968
|
+
if (part === 'day' && segmentValues.month !== null) {
|
969
|
+
return formatter.part(props.dateRef.set({ [part]: value, month: segmentValues.month }), part, {
|
970
|
+
hourCycle: props.hourCycle === 24 ? 'h23' : undefined
|
971
|
+
});
|
972
|
+
}
|
973
|
+
return formatter.part(props.dateRef.set({ [part]: value }), part, {
|
974
|
+
hourCycle: props.hourCycle === 24 ? 'h23' : undefined
|
975
|
+
});
|
976
|
+
}
|
977
|
+
else {
|
978
|
+
return getPlaceholder(part, '', locale());
|
979
|
+
}
|
980
|
+
}
|
981
|
+
else {
|
982
|
+
if (isDateSegmentPart(part)) {
|
983
|
+
const value = segmentValues[part];
|
984
|
+
if (value !== null) {
|
985
|
+
if (part === 'day' && segmentValues.month !== null)
|
986
|
+
/**
|
987
|
+
* As described above, same function
|
988
|
+
*/
|
989
|
+
return formatter.part(props.dateRef.set({ [part]: value, month: segmentValues.month }), part);
|
990
|
+
return formatter.part(props.dateRef.set({ [part]: value }), part);
|
991
|
+
}
|
992
|
+
else {
|
993
|
+
return getPlaceholder(part, '', locale());
|
994
|
+
}
|
995
|
+
}
|
996
|
+
return '';
|
997
|
+
}
|
998
|
+
}
|
999
|
+
const content = Object.keys(segmentValues).reduce((obj, part) => {
|
1000
|
+
if (!isSegmentPart(part))
|
1001
|
+
return obj;
|
1002
|
+
if ('hour' in segmentValues && part === 'dayPeriod') {
|
1003
|
+
const value = segmentValues[part];
|
1004
|
+
if (value !== null)
|
1005
|
+
obj[part] = value;
|
1006
|
+
else
|
1007
|
+
obj[part] = getPlaceholder(part, 'AM', locale());
|
1008
|
+
}
|
1009
|
+
else {
|
1010
|
+
obj[part] = getPartContent(part);
|
1011
|
+
}
|
1012
|
+
return obj;
|
1013
|
+
}, {});
|
1014
|
+
return content;
|
1015
|
+
}
|
1016
|
+
function createContentArr(props) {
|
1017
|
+
const { granularity, formatter, contentObj, hideTimeZone, hourCycle, isTimeValue } = props;
|
1018
|
+
const parts = formatter.toParts(props.dateRef, getOptsByGranularity(granularity, hourCycle, isTimeValue));
|
1019
|
+
const segmentContentArr = parts
|
1020
|
+
.map((part) => {
|
1021
|
+
const defaultParts = ['literal', 'timeZoneName', null];
|
1022
|
+
if (defaultParts.includes(part.type) || !isSegmentPart(part.type)) {
|
1023
|
+
return {
|
1024
|
+
part: part.type,
|
1025
|
+
value: part.value
|
1026
|
+
};
|
1027
|
+
}
|
1028
|
+
return {
|
1029
|
+
part: part.type,
|
1030
|
+
value: contentObj[part.type]
|
1031
|
+
};
|
1032
|
+
})
|
1033
|
+
.filter((segment) => {
|
1034
|
+
if (segment.part === null || segment.value === null)
|
1035
|
+
return false;
|
1036
|
+
if (segment.part === 'timeZoneName' && (!isZonedDateTime(props.dateRef) || hideTimeZone))
|
1037
|
+
return false;
|
1038
|
+
return true;
|
1039
|
+
});
|
1040
|
+
return segmentContentArr;
|
1041
|
+
}
|
1042
|
+
function createContent(props) {
|
1043
|
+
const contentObj = createContentObj(props);
|
1044
|
+
const contentArr = createContentArr({
|
1045
|
+
contentObj,
|
1046
|
+
...props
|
1047
|
+
});
|
1048
|
+
return {
|
1049
|
+
obj: contentObj,
|
1050
|
+
arr: contentArr
|
1051
|
+
};
|
1052
|
+
}
|
1053
|
+
|
1054
|
+
function isSegmentNavigationKey(key) {
|
1055
|
+
if (key === ARROW_RIGHT || key === ARROW_LEFT)
|
1056
|
+
return true;
|
1057
|
+
return false;
|
1058
|
+
}
|
1059
|
+
function isNumberString(value) {
|
1060
|
+
if (Number.isNaN(Number.parseInt(value)))
|
1061
|
+
return false;
|
1062
|
+
return true;
|
1063
|
+
}
|
1064
|
+
function isAcceptableSegmentKey(key) {
|
1065
|
+
const acceptableSegmentKeys = [
|
1066
|
+
ENTER,
|
1067
|
+
ARROW_UP,
|
1068
|
+
ARROW_DOWN,
|
1069
|
+
ARROW_LEFT,
|
1070
|
+
ARROW_RIGHT,
|
1071
|
+
BACKSPACE,
|
1072
|
+
SPACE,
|
1073
|
+
'a',
|
1074
|
+
'A',
|
1075
|
+
'p',
|
1076
|
+
'P'
|
1077
|
+
];
|
1078
|
+
if (acceptableSegmentKeys.includes(key))
|
1079
|
+
return true;
|
1080
|
+
if (isNumberString(key))
|
1081
|
+
return true;
|
1082
|
+
return false;
|
1083
|
+
}
|
1084
|
+
function getSegmentElements(parentElement) {
|
1085
|
+
return Array.from(parentElement.querySelectorAll('[data-rdx-date-field-segment]')).filter((item) => item.getAttribute('data-rdx-date-field-segment') !== 'literal');
|
1086
|
+
}
|
1087
|
+
|
1088
|
+
/*
|
1089
|
+
* Implementation ported from https://github.com/melt-ui/melt-ui/blob/develop/src/lib/internal/helpers/date/types.ts
|
1090
|
+
*/
|
1091
|
+
|
1092
|
+
// https://github.com/unovue/reka-ui/blob/v2/packages/core/src/shared/date/useDateField.ts
|
1093
|
+
function commonSegmentAttrs(props) {
|
1094
|
+
return {
|
1095
|
+
role: 'spinbutton',
|
1096
|
+
contenteditable: true,
|
1097
|
+
tabindex: props.disabled ? undefined : 0,
|
1098
|
+
spellcheck: false,
|
1099
|
+
inputmode: 'numeric',
|
1100
|
+
autocorrect: 'off',
|
1101
|
+
enterkeyhint: 'next',
|
1102
|
+
style: 'caret-color: transparent;'
|
1103
|
+
};
|
1104
|
+
}
|
1105
|
+
function daySegmentAttrs(props) {
|
1106
|
+
const { segmentValues, placeholder } = props;
|
1107
|
+
const isEmpty = segmentValues.day === null;
|
1108
|
+
const date = segmentValues.day ? placeholder.set({ day: segmentValues.day }) : placeholder;
|
1109
|
+
const valueNow = date.day;
|
1110
|
+
const valueMin = 1;
|
1111
|
+
const valueMax = getDaysInMonth(date);
|
1112
|
+
const valueText = isEmpty ? 'Empty' : `${valueNow}`;
|
1113
|
+
return {
|
1114
|
+
...commonSegmentAttrs(props),
|
1115
|
+
'aria-label': 'day,',
|
1116
|
+
'aria-valuemin': valueMin,
|
1117
|
+
'aria-valuemax': valueMax,
|
1118
|
+
'aria-valuenow': valueNow,
|
1119
|
+
'aria-valuetext': valueText,
|
1120
|
+
'data-placeholder': isEmpty ? '' : undefined
|
1121
|
+
};
|
1122
|
+
}
|
1123
|
+
function monthSegmentAttrs(props) {
|
1124
|
+
const { segmentValues, placeholder, formatter } = props;
|
1125
|
+
const isEmpty = segmentValues.month === null;
|
1126
|
+
const date = segmentValues.month ? placeholder.set({ month: segmentValues.month }) : placeholder;
|
1127
|
+
const valueNow = date.month;
|
1128
|
+
const valueMin = 1;
|
1129
|
+
const valueMax = 12;
|
1130
|
+
const valueText = isEmpty ? 'Empty' : `${valueNow} - ${formatter.fullMonth(toDate(date))}`;
|
1131
|
+
return {
|
1132
|
+
...commonSegmentAttrs(props),
|
1133
|
+
'aria-label': 'month, ',
|
1134
|
+
contenteditable: true,
|
1135
|
+
'aria-valuemin': valueMin,
|
1136
|
+
'aria-valuemax': valueMax,
|
1137
|
+
'aria-valuenow': valueNow,
|
1138
|
+
'aria-valuetext': valueText,
|
1139
|
+
'data-placeholder': isEmpty ? '' : undefined
|
1140
|
+
};
|
1141
|
+
}
|
1142
|
+
function yearSegmentAttrs(props) {
|
1143
|
+
const { segmentValues, placeholder } = props;
|
1144
|
+
const isEmpty = segmentValues.year === null;
|
1145
|
+
const date = segmentValues.year ? placeholder.set({ year: segmentValues.year }) : placeholder;
|
1146
|
+
const valueMin = 1;
|
1147
|
+
const valueMax = 9999;
|
1148
|
+
const valueNow = date.year;
|
1149
|
+
const valueText = isEmpty ? 'Empty' : `${valueNow}`;
|
1150
|
+
return {
|
1151
|
+
...commonSegmentAttrs(props),
|
1152
|
+
'aria-label': 'year, ',
|
1153
|
+
'aria-valuemin': valueMin,
|
1154
|
+
'aria-valuemax': valueMax,
|
1155
|
+
'aria-valuenow': valueNow,
|
1156
|
+
'aria-valuetext': valueText,
|
1157
|
+
'data-placeholder': isEmpty ? '' : undefined
|
1158
|
+
};
|
1159
|
+
}
|
1160
|
+
function hourSegmentAttrs(props) {
|
1161
|
+
const { segmentValues, hourCycle, placeholder } = props;
|
1162
|
+
if (!('hour' in segmentValues) || !('hour' in placeholder))
|
1163
|
+
return {};
|
1164
|
+
const isEmpty = segmentValues.hour === null;
|
1165
|
+
const date = segmentValues.hour ? placeholder.set({ hour: segmentValues.hour }) : placeholder;
|
1166
|
+
const valueMin = hourCycle === 12 ? 1 : 0;
|
1167
|
+
const valueMax = hourCycle === 12 ? 12 : 23;
|
1168
|
+
const valueNow = date.hour;
|
1169
|
+
const valueText = isEmpty ? 'Empty' : `${valueNow} ${segmentValues.dayPeriod ?? ''}`;
|
1170
|
+
return {
|
1171
|
+
...commonSegmentAttrs(props),
|
1172
|
+
'aria-label': 'hour, ',
|
1173
|
+
'aria-valuemin': valueMin,
|
1174
|
+
'aria-valuemax': valueMax,
|
1175
|
+
'aria-valuenow': valueNow,
|
1176
|
+
'aria-valuetext': valueText,
|
1177
|
+
'data-placeholder': isEmpty ? '' : undefined
|
1178
|
+
};
|
1179
|
+
}
|
1180
|
+
function minuteSegmentAttrs(props) {
|
1181
|
+
const { segmentValues, placeholder } = props;
|
1182
|
+
if (!('minute' in segmentValues) || !('minute' in placeholder))
|
1183
|
+
return {};
|
1184
|
+
const isEmpty = segmentValues.minute === null;
|
1185
|
+
const date = segmentValues.minute ? placeholder.set({ minute: segmentValues.minute }) : placeholder;
|
1186
|
+
const valueNow = date.minute;
|
1187
|
+
const valueMin = 0;
|
1188
|
+
const valueMax = 59;
|
1189
|
+
const valueText = isEmpty ? 'Empty' : `${valueNow}`;
|
1190
|
+
return {
|
1191
|
+
...commonSegmentAttrs(props),
|
1192
|
+
'aria-label': 'minute, ',
|
1193
|
+
'aria-valuemin': valueMin,
|
1194
|
+
'aria-valuemax': valueMax,
|
1195
|
+
'aria-valuenow': valueNow,
|
1196
|
+
'aria-valuetext': valueText,
|
1197
|
+
'data-placeholder': isEmpty ? '' : undefined
|
1198
|
+
};
|
1199
|
+
}
|
1200
|
+
function secondSegmentAttrs(props) {
|
1201
|
+
const { segmentValues, placeholder } = props;
|
1202
|
+
if (!('second' in segmentValues) || !('second' in placeholder))
|
1203
|
+
return {};
|
1204
|
+
const isEmpty = segmentValues.second === null;
|
1205
|
+
const date = segmentValues.second ? placeholder.set({ second: segmentValues.second }) : placeholder;
|
1206
|
+
const valueNow = date.second;
|
1207
|
+
const valueMin = 0;
|
1208
|
+
const valueMax = 59;
|
1209
|
+
const valueText = isEmpty ? 'Empty' : `${valueNow}`;
|
1210
|
+
return {
|
1211
|
+
...commonSegmentAttrs(props),
|
1212
|
+
'aria-label': 'second, ',
|
1213
|
+
'aria-valuemin': valueMin,
|
1214
|
+
'aria-valuemax': valueMax,
|
1215
|
+
'aria-valuenow': valueNow,
|
1216
|
+
'aria-valuetext': valueText,
|
1217
|
+
'data-placeholder': isEmpty ? '' : undefined
|
1218
|
+
};
|
1219
|
+
}
|
1220
|
+
function dayPeriodSegmentAttrs(props) {
|
1221
|
+
const { segmentValues } = props;
|
1222
|
+
if (!('dayPeriod' in segmentValues))
|
1223
|
+
return {};
|
1224
|
+
const valueMin = 0;
|
1225
|
+
const valueMax = 12;
|
1226
|
+
const valueNow = segmentValues.hour ? (segmentValues.hour > 12 ? segmentValues.hour - 12 : segmentValues.hour) : 0;
|
1227
|
+
const valueText = segmentValues.dayPeriod ?? 'AM';
|
1228
|
+
return {
|
1229
|
+
...commonSegmentAttrs(props),
|
1230
|
+
inputmode: 'text',
|
1231
|
+
'aria-label': 'AM/PM',
|
1232
|
+
'aria-valuemin': valueMin,
|
1233
|
+
'aria-valuemax': valueMax,
|
1234
|
+
'aria-valuenow': valueNow,
|
1235
|
+
'aria-valuetext': valueText
|
1236
|
+
};
|
1237
|
+
}
|
1238
|
+
function literalSegmentAttrs(_props) {
|
1239
|
+
return {
|
1240
|
+
'aria-hidden': true,
|
1241
|
+
'data-segment': 'literal'
|
1242
|
+
};
|
1243
|
+
}
|
1244
|
+
function timeZoneSegmentAttrs(props) {
|
1245
|
+
return {
|
1246
|
+
role: 'textbox',
|
1247
|
+
'aria-label': 'timezone, ',
|
1248
|
+
'data-readonly': true,
|
1249
|
+
'data-segment': 'timeZoneName',
|
1250
|
+
tabindex: props.disabled ? undefined : 0,
|
1251
|
+
style: 'caret-color: transparent;'
|
1252
|
+
};
|
1253
|
+
}
|
1254
|
+
function eraSegmentAttrs(props) {
|
1255
|
+
const { segmentValues, placeholder } = props;
|
1256
|
+
const valueMin = 0;
|
1257
|
+
const valueMax = 0;
|
1258
|
+
const valueNow = 0;
|
1259
|
+
const valueText = 'era' in segmentValues ? segmentValues.era : placeholder.era;
|
1260
|
+
return {
|
1261
|
+
...commonSegmentAttrs(props),
|
1262
|
+
'aria-label': 'era',
|
1263
|
+
'aria-valuemin': valueMin,
|
1264
|
+
'aria-valuemax': valueMax,
|
1265
|
+
'aria-valuenow': valueNow,
|
1266
|
+
'aria-valuetext': valueText
|
1267
|
+
};
|
1268
|
+
}
|
1269
|
+
const segmentBuilders = {
|
1270
|
+
day: {
|
1271
|
+
attrs: daySegmentAttrs
|
1272
|
+
},
|
1273
|
+
month: {
|
1274
|
+
attrs: monthSegmentAttrs
|
1275
|
+
},
|
1276
|
+
year: {
|
1277
|
+
attrs: yearSegmentAttrs
|
1278
|
+
},
|
1279
|
+
hour: {
|
1280
|
+
attrs: hourSegmentAttrs
|
1281
|
+
},
|
1282
|
+
minute: {
|
1283
|
+
attrs: minuteSegmentAttrs
|
1284
|
+
},
|
1285
|
+
second: {
|
1286
|
+
attrs: secondSegmentAttrs
|
1287
|
+
},
|
1288
|
+
dayPeriod: {
|
1289
|
+
attrs: dayPeriodSegmentAttrs
|
1290
|
+
},
|
1291
|
+
literal: {
|
1292
|
+
attrs: literalSegmentAttrs
|
1293
|
+
},
|
1294
|
+
timeZoneName: {
|
1295
|
+
attrs: timeZoneSegmentAttrs
|
1296
|
+
},
|
1297
|
+
era: {
|
1298
|
+
attrs: eraSegmentAttrs
|
1299
|
+
}
|
1300
|
+
};
|
1301
|
+
function useDateField(props) {
|
1302
|
+
function handleSegmentClick(e) {
|
1303
|
+
const disabled = props.disabled();
|
1304
|
+
if (disabled)
|
1305
|
+
e.preventDefault();
|
1306
|
+
}
|
1307
|
+
function deleteValue(prevValue) {
|
1308
|
+
props.hasLeftFocus.set(false);
|
1309
|
+
if (prevValue === null)
|
1310
|
+
return prevValue;
|
1311
|
+
const str = prevValue.toString();
|
1312
|
+
if (str.length === 1) {
|
1313
|
+
props.modelValue.set(undefined);
|
1314
|
+
return null;
|
1315
|
+
}
|
1316
|
+
return Number.parseInt(str.slice(0, -1));
|
1317
|
+
}
|
1318
|
+
function dateTimeValueIncrementation({ e, part, dateRef, prevValue, hourCycle }) {
|
1319
|
+
const sign = e.key === ARROW_UP ? 1 : -1;
|
1320
|
+
if (prevValue === null)
|
1321
|
+
return dateRef[part];
|
1322
|
+
if (part === 'hour' && 'hour' in dateRef) {
|
1323
|
+
const cycleArgs = [
|
1324
|
+
part,
|
1325
|
+
sign,
|
1326
|
+
{ hourCycle }
|
1327
|
+
];
|
1328
|
+
return dateRef.set({ [part]: prevValue }).cycle(...cycleArgs)[part];
|
1329
|
+
}
|
1330
|
+
const cycleArgs = [part, sign];
|
1331
|
+
if (part === 'day' && props.segmentValues().month !== null)
|
1332
|
+
return dateRef
|
1333
|
+
.set({ [part]: prevValue, month: props.segmentValues().month })
|
1334
|
+
.cycle(...cycleArgs)[part];
|
1335
|
+
return dateRef.set({ [part]: prevValue }).cycle(...cycleArgs)[part];
|
1336
|
+
}
|
1337
|
+
function updateDayOrMonth(max, num, prev) {
|
1338
|
+
let moveToNext = false;
|
1339
|
+
const maxStart = Math.floor(max / 10);
|
1340
|
+
/**
|
1341
|
+
* If the user has left the segment, we want to reset the
|
1342
|
+
* `prev` value so that we can start the segment over again
|
1343
|
+
* when the user types a number.
|
1344
|
+
*/
|
1345
|
+
if (props.hasLeftFocus()) {
|
1346
|
+
props.hasLeftFocus.set(false);
|
1347
|
+
prev = null;
|
1348
|
+
}
|
1349
|
+
if (prev === null) {
|
1350
|
+
/**
|
1351
|
+
* If the user types a 0 as the first number, we want
|
1352
|
+
* to keep track of that so that when they type the next
|
1353
|
+
* number, we can move to the next segment.
|
1354
|
+
*/
|
1355
|
+
if (num === 0) {
|
1356
|
+
props.lastKeyZero.set(true);
|
1357
|
+
return { value: null, moveToNext };
|
1358
|
+
}
|
1359
|
+
/**
|
1360
|
+
* If the last key was a 0, or if the first number is
|
1361
|
+
* greater than the max start digit (0-3 in most cases), then
|
1362
|
+
* we want to move to the next segment, since it's not possible
|
1363
|
+
* to continue typing a valid number in this segment.
|
1364
|
+
*/
|
1365
|
+
if (props.lastKeyZero() || num > maxStart) {
|
1366
|
+
// move to next
|
1367
|
+
moveToNext = true;
|
1368
|
+
}
|
1369
|
+
props.lastKeyZero.set(false);
|
1370
|
+
/**
|
1371
|
+
* If none of the above conditions are met, then we can just
|
1372
|
+
* return the number as the segment value and continue typing
|
1373
|
+
* in this segment.
|
1374
|
+
*/
|
1375
|
+
return { value: num, moveToNext };
|
1376
|
+
}
|
1377
|
+
/**
|
1378
|
+
* If the number of digits is 2, or if the total with the existing digit
|
1379
|
+
* and the pressed digit is greater than the maximum value for this
|
1380
|
+
* month, then we will reset the segment as if the user had pressed the
|
1381
|
+
* backspace key and then typed the number.
|
1382
|
+
*/
|
1383
|
+
const digits = prev.toString().length;
|
1384
|
+
const total = Number.parseInt(prev.toString() + num.toString());
|
1385
|
+
/**
|
1386
|
+
* If the number of digits is 2, or if the total with the existing digit
|
1387
|
+
* and the pressed digit is greater than the maximum value for this
|
1388
|
+
* month, then we will reset the segment as if the user had pressed the
|
1389
|
+
* backspace key and then typed the number.
|
1390
|
+
*/
|
1391
|
+
if (digits === 2 || total > max) {
|
1392
|
+
/**
|
1393
|
+
* As we're doing elsewhere, we're checking if the number is greater
|
1394
|
+
* than the max start digit (0-3 in most months), and if so, we're
|
1395
|
+
* going to move to the next segment.
|
1396
|
+
*/
|
1397
|
+
if (num > maxStart || total > max) {
|
1398
|
+
// move to next
|
1399
|
+
moveToNext = true;
|
1400
|
+
}
|
1401
|
+
return { value: num, moveToNext };
|
1402
|
+
}
|
1403
|
+
// move to next
|
1404
|
+
moveToNext = true;
|
1405
|
+
return { value: total, moveToNext };
|
1406
|
+
}
|
1407
|
+
function updateYear(num, prev) {
|
1408
|
+
let moveToNext = false;
|
1409
|
+
/**
|
1410
|
+
* If the user has left the segment, we want to reset the
|
1411
|
+
* `prev` value so that we can start the segment over again
|
1412
|
+
* when the user types a number.
|
1413
|
+
*/
|
1414
|
+
// probably not implement, kind of weird
|
1415
|
+
if (props.hasLeftFocus()) {
|
1416
|
+
props.hasLeftFocus.set(false);
|
1417
|
+
prev = null;
|
1418
|
+
}
|
1419
|
+
if (prev === null)
|
1420
|
+
return { value: num === 0 ? 1 : num, moveToNext };
|
1421
|
+
const str = prev.toString() + num.toString();
|
1422
|
+
if (str.length > 4)
|
1423
|
+
return { value: num === 0 ? 1 : num, moveToNext };
|
1424
|
+
if (str.length === 4)
|
1425
|
+
moveToNext = true;
|
1426
|
+
const int = Number.parseInt(str);
|
1427
|
+
return { value: int, moveToNext };
|
1428
|
+
}
|
1429
|
+
function updateHour(num, prev) {
|
1430
|
+
const max = 24;
|
1431
|
+
let moveToNext = false;
|
1432
|
+
const maxStart = Math.floor(max / 10);
|
1433
|
+
/**
|
1434
|
+
* If the user has left the segment, we want to reset the
|
1435
|
+
* `prev` value so that we can start the segment over again
|
1436
|
+
* when the user types a number.
|
1437
|
+
*/
|
1438
|
+
// probably not implement, kind of weird
|
1439
|
+
if (props.hasLeftFocus()) {
|
1440
|
+
props.hasLeftFocus.set(false);
|
1441
|
+
prev = null;
|
1442
|
+
}
|
1443
|
+
if (prev === null) {
|
1444
|
+
/**
|
1445
|
+
* If the user types a 0 as the first number, we want
|
1446
|
+
* to keep track of that so that when they type the next
|
1447
|
+
* number, we can move to the next segment.
|
1448
|
+
*/
|
1449
|
+
if (num === 0) {
|
1450
|
+
props.lastKeyZero.set(true);
|
1451
|
+
return { value: 0, moveToNext };
|
1452
|
+
}
|
1453
|
+
/**
|
1454
|
+
* If the last key was a 0, or if the first number is
|
1455
|
+
* greater than the max start digit (0-3 in most cases), then
|
1456
|
+
* we want to move to the next segment, since it's not possible
|
1457
|
+
* to continue typing a valid number in this segment.
|
1458
|
+
*/
|
1459
|
+
if (props.lastKeyZero() || num > maxStart) {
|
1460
|
+
// move to next
|
1461
|
+
moveToNext = true;
|
1462
|
+
}
|
1463
|
+
props.lastKeyZero.set(false);
|
1464
|
+
/**
|
1465
|
+
* If none of the above conditions are met, then we can just
|
1466
|
+
* return the number as the segment value and continue typing
|
1467
|
+
* in this segment.
|
1468
|
+
*/
|
1469
|
+
return { value: num, moveToNext };
|
1470
|
+
}
|
1471
|
+
/**
|
1472
|
+
* If the number of digits is 2, or if the total with the existing digit
|
1473
|
+
* and the pressed digit is greater than the maximum value for this
|
1474
|
+
* month, then we will reset the segment as if the user had pressed the
|
1475
|
+
* backspace key and then typed the number.
|
1476
|
+
*/
|
1477
|
+
const digits = prev.toString().length;
|
1478
|
+
const total = Number.parseInt(prev.toString() + num.toString());
|
1479
|
+
/**
|
1480
|
+
* If the number of digits is 2, or if the total with the existing digit
|
1481
|
+
* and the pressed digit is greater than the maximum value for this
|
1482
|
+
* month, then we will reset the segment as if the user had pressed the
|
1483
|
+
* backspace key and then typed the number.
|
1484
|
+
*/
|
1485
|
+
if (digits === 2 || total > max) {
|
1486
|
+
/**
|
1487
|
+
* As we're doing elsewhere, we're checking if the number is greater
|
1488
|
+
* than the max start digit (0-3 in most months), and if so, we're
|
1489
|
+
* going to move to the next segment.
|
1490
|
+
*/
|
1491
|
+
if (num > maxStart) {
|
1492
|
+
// move to next
|
1493
|
+
moveToNext = true;
|
1494
|
+
}
|
1495
|
+
return { value: num, moveToNext };
|
1496
|
+
}
|
1497
|
+
// move to next
|
1498
|
+
moveToNext = true;
|
1499
|
+
return { value: total, moveToNext };
|
1500
|
+
}
|
1501
|
+
function updateMinuteOrSecond(num, prev) {
|
1502
|
+
const max = 59;
|
1503
|
+
let moveToNext = false;
|
1504
|
+
const maxStart = Math.floor(max / 10);
|
1505
|
+
/**
|
1506
|
+
* If the user has left the segment, we want to reset the
|
1507
|
+
* `prev` value so that we can start the segment over again
|
1508
|
+
* when the user types a number.
|
1509
|
+
*/
|
1510
|
+
if (props.hasLeftFocus()) {
|
1511
|
+
props.hasLeftFocus.set(false);
|
1512
|
+
prev = null;
|
1513
|
+
}
|
1514
|
+
if (prev === null) {
|
1515
|
+
/**
|
1516
|
+
* If the user types a 0 as the first number, we want
|
1517
|
+
* to keep track of that so that when they type the next
|
1518
|
+
* number, we can move to the next segment.
|
1519
|
+
*/
|
1520
|
+
if (num === 0) {
|
1521
|
+
props.lastKeyZero.set(true);
|
1522
|
+
return { value: 0, moveToNext };
|
1523
|
+
}
|
1524
|
+
/**
|
1525
|
+
* If the last key was a 0, or if the first number is
|
1526
|
+
* greater than the max start digit (0-3 in most cases), then
|
1527
|
+
* we want to move to the next segment, since it's not possible
|
1528
|
+
* to continue typing a valid number in this segment.
|
1529
|
+
*/
|
1530
|
+
if (props.lastKeyZero() || num > maxStart) {
|
1531
|
+
// move to next
|
1532
|
+
moveToNext = true;
|
1533
|
+
}
|
1534
|
+
props.lastKeyZero.set(false);
|
1535
|
+
/**
|
1536
|
+
* If none of the above conditions are met, then we can just
|
1537
|
+
* return the number as the segment value and continue typing
|
1538
|
+
* in this segment.
|
1539
|
+
*/
|
1540
|
+
return { value: num, moveToNext };
|
1541
|
+
}
|
1542
|
+
/**
|
1543
|
+
* If the number of digits is 2, or if the total with the existing digit
|
1544
|
+
* and the pressed digit is greater than the maximum value for this
|
1545
|
+
* month, then we will reset the segment as if the user had pressed the
|
1546
|
+
* backspace key and then typed the number.
|
1547
|
+
*/
|
1548
|
+
const digits = prev.toString().length;
|
1549
|
+
const total = Number.parseInt(prev.toString() + num.toString());
|
1550
|
+
/**
|
1551
|
+
* If the number of digits is 2, or if the total with the existing digit
|
1552
|
+
* and the pressed digit is greater than the maximum value for this
|
1553
|
+
* month, then we will reset the segment as if the user had pressed the
|
1554
|
+
* backspace key and then typed the number.
|
1555
|
+
*/
|
1556
|
+
if (digits === 2 || total > max) {
|
1557
|
+
/**
|
1558
|
+
* As we're doing elsewhere, we're checking if the number is greater
|
1559
|
+
* than the max start digit (0-3 in most months), and if so, we're
|
1560
|
+
* going to move to the next segment.
|
1561
|
+
*/
|
1562
|
+
if (num > maxStart) {
|
1563
|
+
// move to next
|
1564
|
+
moveToNext = true;
|
1565
|
+
}
|
1566
|
+
return { value: num, moveToNext };
|
1567
|
+
}
|
1568
|
+
// move to next
|
1569
|
+
moveToNext = true;
|
1570
|
+
return { value: total, moveToNext };
|
1571
|
+
}
|
1572
|
+
function minuteSecondIncrementation({ e, part, dateRef, prevValue }) {
|
1573
|
+
const sign = e.key === ARROW_UP ? 1 : -1;
|
1574
|
+
const min = 0;
|
1575
|
+
const max = 59;
|
1576
|
+
if (prevValue === null)
|
1577
|
+
return sign > 0 ? min : max;
|
1578
|
+
const cycleArgs = [part, sign];
|
1579
|
+
return dateRef.set({ [part]: prevValue }).cycle(...cycleArgs)[part];
|
1580
|
+
}
|
1581
|
+
const attributes = computed(() => segmentBuilders[props.part]?.attrs({
|
1582
|
+
disabled: props.disabled(),
|
1583
|
+
placeholder: props.placeholder(),
|
1584
|
+
hourCycle: props.hourCycle,
|
1585
|
+
segmentValues: props.segmentValues(),
|
1586
|
+
formatter: props.formatter
|
1587
|
+
}) ?? {});
|
1588
|
+
function handleMonthSegmentKeydown(e) {
|
1589
|
+
if (!isAcceptableSegmentKey(e.key) || isSegmentNavigationKey(e.key))
|
1590
|
+
return;
|
1591
|
+
const prevValue = props.segmentValues().month;
|
1592
|
+
if (e.key === ARROW_DOWN || e.key === ARROW_UP) {
|
1593
|
+
props.segmentValues.update((prev) => ({
|
1594
|
+
...prev,
|
1595
|
+
month: dateTimeValueIncrementation({
|
1596
|
+
e,
|
1597
|
+
part: 'month',
|
1598
|
+
dateRef: props.placeholder(),
|
1599
|
+
prevValue
|
1600
|
+
})
|
1601
|
+
}));
|
1602
|
+
return;
|
1603
|
+
}
|
1604
|
+
if (isNumberString(e.key)) {
|
1605
|
+
const num = Number.parseInt(e.key);
|
1606
|
+
const { value, moveToNext } = updateDayOrMonth(12, num, prevValue);
|
1607
|
+
props.segmentValues.update((prev) => ({ ...prev, month: value }));
|
1608
|
+
if (moveToNext)
|
1609
|
+
props.focusNext();
|
1610
|
+
}
|
1611
|
+
if (e.key === BACKSPACE) {
|
1612
|
+
props.hasLeftFocus.set(false);
|
1613
|
+
props.segmentValues.update((prev) => ({ ...prev, month: deleteValue(prevValue) }));
|
1614
|
+
}
|
1615
|
+
}
|
1616
|
+
function handleDaySegmentKeydown(e) {
|
1617
|
+
if (!isAcceptableSegmentKey(e.key) || isSegmentNavigationKey(e.key))
|
1618
|
+
return;
|
1619
|
+
const prevValue = props.segmentValues().day;
|
1620
|
+
if (e.key === ARROW_DOWN || e.key === ARROW_UP) {
|
1621
|
+
props.segmentValues.update((prev) => ({
|
1622
|
+
...prev,
|
1623
|
+
day: dateTimeValueIncrementation({
|
1624
|
+
e,
|
1625
|
+
part: 'day',
|
1626
|
+
dateRef: props.placeholder(),
|
1627
|
+
prevValue
|
1628
|
+
})
|
1629
|
+
}));
|
1630
|
+
return;
|
1631
|
+
}
|
1632
|
+
if (isNumberString(e.key)) {
|
1633
|
+
const num = Number.parseInt(e.key);
|
1634
|
+
const segmentMonthValue = props.segmentValues().month;
|
1635
|
+
const daysInMonth = segmentMonthValue
|
1636
|
+
? getDaysInMonth(props.placeholder().set({ month: segmentMonthValue }))
|
1637
|
+
: getDaysInMonth(props.placeholder());
|
1638
|
+
const { value, moveToNext } = updateDayOrMonth(daysInMonth, num, prevValue);
|
1639
|
+
props.segmentValues.update((prev) => ({ ...prev, day: value }));
|
1640
|
+
if (moveToNext)
|
1641
|
+
props.focusNext();
|
1642
|
+
}
|
1643
|
+
if (e.key === BACKSPACE) {
|
1644
|
+
props.hasLeftFocus.set(false);
|
1645
|
+
props.segmentValues.update((prev) => ({ ...prev, day: deleteValue(prevValue) }));
|
1646
|
+
}
|
1647
|
+
}
|
1648
|
+
function handleYearSegmentKeydown(e) {
|
1649
|
+
if (!isAcceptableSegmentKey(e.key) || isSegmentNavigationKey(e.key))
|
1650
|
+
return;
|
1651
|
+
const prevValue = props.segmentValues().year;
|
1652
|
+
if (e.key === ARROW_DOWN || e.key === ARROW_UP) {
|
1653
|
+
props.segmentValues.update((prev) => ({
|
1654
|
+
...prev,
|
1655
|
+
year: dateTimeValueIncrementation({
|
1656
|
+
e,
|
1657
|
+
part: 'year',
|
1658
|
+
dateRef: props.placeholder(),
|
1659
|
+
prevValue
|
1660
|
+
})
|
1661
|
+
}));
|
1662
|
+
return;
|
1663
|
+
}
|
1664
|
+
if (isNumberString(e.key)) {
|
1665
|
+
const num = Number.parseInt(e.key);
|
1666
|
+
const { value, moveToNext } = updateYear(num, prevValue);
|
1667
|
+
props.segmentValues.update((prev) => ({ ...prev, year: value }));
|
1668
|
+
if (moveToNext)
|
1669
|
+
props.focusNext();
|
1670
|
+
}
|
1671
|
+
if (e.key === BACKSPACE) {
|
1672
|
+
props.hasLeftFocus.set(false);
|
1673
|
+
props.segmentValues.update((prev) => ({ ...prev, year: deleteValue(prevValue) }));
|
1674
|
+
}
|
1675
|
+
}
|
1676
|
+
function handleHourSegmentKeydown(e) {
|
1677
|
+
const dateRef = props.placeholder();
|
1678
|
+
const values = props.segmentValues();
|
1679
|
+
if (!isAcceptableSegmentKey(e.key) ||
|
1680
|
+
isSegmentNavigationKey(e.key) ||
|
1681
|
+
!('hour' in dateRef) ||
|
1682
|
+
!('hour' in values))
|
1683
|
+
return;
|
1684
|
+
const prevValue = values.hour;
|
1685
|
+
const hourCycle = props.hourCycle;
|
1686
|
+
if (e.key === ARROW_UP || e.key === ARROW_DOWN) {
|
1687
|
+
props.segmentValues.update((prev) => ({
|
1688
|
+
...prev,
|
1689
|
+
hour: dateTimeValueIncrementation({
|
1690
|
+
e,
|
1691
|
+
part: 'hour',
|
1692
|
+
dateRef: props.placeholder(),
|
1693
|
+
prevValue,
|
1694
|
+
hourCycle
|
1695
|
+
})
|
1696
|
+
}));
|
1697
|
+
if ('dayPeriod' in props.segmentValues() && values.hour != null) {
|
1698
|
+
if (values.hour < 12)
|
1699
|
+
props.segmentValues.update((prev) => ({ ...prev, dayPeriod: 'AM' }));
|
1700
|
+
else if (values.hour)
|
1701
|
+
props.segmentValues.update((prev) => ({ ...prev, dayPeriod: 'PM' }));
|
1702
|
+
}
|
1703
|
+
return;
|
1704
|
+
}
|
1705
|
+
if (isNumberString(e.key)) {
|
1706
|
+
const num = Number.parseInt(e.key);
|
1707
|
+
const { value, moveToNext } = updateHour(num, prevValue);
|
1708
|
+
if ('dayPeriod' in props.segmentValues() && value && value > 12)
|
1709
|
+
props.segmentValues.update((prev) => ({ ...prev, dayPeriod: 'AM' }));
|
1710
|
+
else if ('dayPeriod' in props.segmentValues() && value)
|
1711
|
+
props.segmentValues.update((prev) => ({ ...prev, dayPeriod: 'PM' }));
|
1712
|
+
props.segmentValues.update((prev) => ({ ...prev, hour: value }));
|
1713
|
+
if (moveToNext)
|
1714
|
+
props.focusNext();
|
1715
|
+
}
|
1716
|
+
if (e.key === BACKSPACE) {
|
1717
|
+
props.hasLeftFocus.set(false);
|
1718
|
+
props.segmentValues.update((prev) => ({ ...prev, hour: deleteValue(prevValue) }));
|
1719
|
+
}
|
1720
|
+
}
|
1721
|
+
function handleMinuteSegmentKeydown(e) {
|
1722
|
+
const dateRef = props.placeholder();
|
1723
|
+
const values = props.segmentValues();
|
1724
|
+
if (!isAcceptableSegmentKey(e.key) ||
|
1725
|
+
isSegmentNavigationKey(e.key) ||
|
1726
|
+
!('minute' in dateRef) ||
|
1727
|
+
!('minute' in values))
|
1728
|
+
return;
|
1729
|
+
const prevValue = values.minute;
|
1730
|
+
props.segmentValues.update((prev) => ({
|
1731
|
+
...prev,
|
1732
|
+
minute: minuteSecondIncrementation({
|
1733
|
+
e,
|
1734
|
+
part: 'minute',
|
1735
|
+
dateRef: props.placeholder(),
|
1736
|
+
prevValue
|
1737
|
+
})
|
1738
|
+
}));
|
1739
|
+
if (isNumberString(e.key)) {
|
1740
|
+
const num = Number.parseInt(e.key);
|
1741
|
+
const { value, moveToNext } = updateMinuteOrSecond(num, prevValue);
|
1742
|
+
props.segmentValues.update((prev) => ({ ...prev, minute: value }));
|
1743
|
+
if (moveToNext)
|
1744
|
+
props.focusNext();
|
1745
|
+
}
|
1746
|
+
if (e.key === BACKSPACE) {
|
1747
|
+
props.hasLeftFocus.set(false);
|
1748
|
+
props.segmentValues.update((prev) => ({ ...prev, minute: deleteValue(prevValue) }));
|
1749
|
+
}
|
1750
|
+
}
|
1751
|
+
function handleSecondSegmentKeydown(e) {
|
1752
|
+
const dateRef = props.placeholder();
|
1753
|
+
const values = props.segmentValues();
|
1754
|
+
if (!isAcceptableSegmentKey(e.key) ||
|
1755
|
+
isSegmentNavigationKey(e.key) ||
|
1756
|
+
!('second' in dateRef) ||
|
1757
|
+
!('second' in values))
|
1758
|
+
return;
|
1759
|
+
const prevValue = values.second;
|
1760
|
+
props.segmentValues.update((prev) => ({
|
1761
|
+
...prev,
|
1762
|
+
second: minuteSecondIncrementation({
|
1763
|
+
e,
|
1764
|
+
part: 'second',
|
1765
|
+
dateRef: props.placeholder(),
|
1766
|
+
prevValue
|
1767
|
+
})
|
1768
|
+
}));
|
1769
|
+
if (isNumberString(e.key)) {
|
1770
|
+
const num = Number.parseInt(e.key);
|
1771
|
+
const { value, moveToNext } = updateMinuteOrSecond(num, prevValue);
|
1772
|
+
props.segmentValues.update((prev) => ({ ...prev, second: value }));
|
1773
|
+
if (moveToNext)
|
1774
|
+
props.focusNext();
|
1775
|
+
}
|
1776
|
+
if (e.key === BACKSPACE) {
|
1777
|
+
props.hasLeftFocus.set(false);
|
1778
|
+
props.segmentValues.update((prev) => ({ ...prev, second: deleteValue(prevValue) }));
|
1779
|
+
}
|
1780
|
+
}
|
1781
|
+
function handleDayPeriodSegmentKeydown(e) {
|
1782
|
+
if (((!isAcceptableSegmentKey(e.key) || isSegmentNavigationKey(e.key)) && e.key !== 'a' && e.key !== 'p') ||
|
1783
|
+
!('hour' in props.placeholder()) ||
|
1784
|
+
!('dayPeriod' in props.segmentValues()))
|
1785
|
+
return;
|
1786
|
+
const values = props.segmentValues();
|
1787
|
+
if (e.key === ARROW_UP || e.key === ARROW_DOWN) {
|
1788
|
+
if (values.dayPeriod === 'AM') {
|
1789
|
+
props.segmentValues.update((prev) => ({ ...prev, dayPeriod: 'PM' }));
|
1790
|
+
props.segmentValues.update((prev) => ({ ...prev, hour: values.hour + 12 }));
|
1791
|
+
return;
|
1792
|
+
}
|
1793
|
+
props.segmentValues.update((prev) => ({ ...prev, dayPeriod: 'AM' }));
|
1794
|
+
props.segmentValues.update((prev) => ({ ...prev, hour: values.hour - 12 }));
|
1795
|
+
return;
|
1796
|
+
}
|
1797
|
+
if (['a', 'A'].includes(e.key) && values.dayPeriod !== 'AM') {
|
1798
|
+
props.segmentValues.update((prev) => ({ ...prev, dayPeriod: 'AM' }));
|
1799
|
+
props.segmentValues.update((prev) => ({ ...prev, hour: values.hour - 12 }));
|
1800
|
+
return;
|
1801
|
+
}
|
1802
|
+
if (['p', 'P'].includes(e.key) && values.dayPeriod !== 'PM') {
|
1803
|
+
props.segmentValues.update((prev) => ({ ...prev, dayPeriod: 'PM' }));
|
1804
|
+
props.segmentValues.update((prev) => ({ ...prev, hour: values.hour + 12 }));
|
1805
|
+
}
|
1806
|
+
}
|
1807
|
+
function handleSegmentKeydown(e) {
|
1808
|
+
const disabled = props.disabled();
|
1809
|
+
const readonly = props.readonly();
|
1810
|
+
if (e.key !== TAB)
|
1811
|
+
e.preventDefault();
|
1812
|
+
if (disabled || readonly)
|
1813
|
+
return;
|
1814
|
+
const segmentKeydownHandlers = {
|
1815
|
+
month: handleMonthSegmentKeydown,
|
1816
|
+
day: handleDaySegmentKeydown,
|
1817
|
+
year: handleYearSegmentKeydown,
|
1818
|
+
hour: handleHourSegmentKeydown,
|
1819
|
+
minute: handleMinuteSegmentKeydown,
|
1820
|
+
second: handleSecondSegmentKeydown,
|
1821
|
+
dayPeriod: handleDayPeriodSegmentKeydown,
|
1822
|
+
timeZoneName: () => { }
|
1823
|
+
};
|
1824
|
+
segmentKeydownHandlers[props.part](e);
|
1825
|
+
if (![ARROW_LEFT, ARROW_RIGHT].includes(e.key) &&
|
1826
|
+
e.key !== TAB &&
|
1827
|
+
e.key !== SHIFT &&
|
1828
|
+
isAcceptableSegmentKey(e.key)) {
|
1829
|
+
if (Object.values(props.segmentValues()).every((item) => item !== null)) {
|
1830
|
+
const updateObject = { ...props.segmentValues() };
|
1831
|
+
let dateRef = props.placeholder().copy();
|
1832
|
+
Object.keys(updateObject).forEach((part) => {
|
1833
|
+
const value = updateObject[part];
|
1834
|
+
dateRef = dateRef.set({ [part]: value });
|
1835
|
+
});
|
1836
|
+
props.modelValue.set(dateRef.copy());
|
1837
|
+
}
|
1838
|
+
}
|
1839
|
+
}
|
1840
|
+
return {
|
1841
|
+
handleSegmentClick,
|
1842
|
+
handleSegmentKeydown,
|
1843
|
+
attributes
|
1844
|
+
};
|
1845
|
+
}
|
1846
|
+
|
758
1847
|
var RdxPositionSide;
|
759
1848
|
(function (RdxPositionSide) {
|
760
1849
|
RdxPositionSide["Top"] = "top";
|
@@ -1011,5 +2100,5 @@ function watch(deps, fn, options) {
|
|
1011
2100
|
* Generated bundle index. Do not edit.
|
1012
2101
|
*/
|
1013
2102
|
|
1014
|
-
export { A, ALT, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, ASTERISK, BACKSPACE, CAPS_LOCK, CONTROL, CTRL, DELETE, END, ENTER, ESCAPE, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, HOME, META, P, PAGE_DOWN, PAGE_UP, RDX_POSITIONING_DEFAULTS, RDX_POSITIONS, RdxAutoFocusDirective, RdxFocusInitialDirective, RdxPositionAlign, RdxPositionSide, SHIFT, SPACE, SPACE_CODE, TAB, WINDOW, _IdGenerator, a, areAllDaysBetweenValid, createFormatter, createMonth, createMonths, getAllPossibleConnectedPositions, getArrowPositionParams, getContentPosition, getDaysBetween, getDaysInMonth, getDefaultDate, getLastFirstDayOfWeek, getNextLastDayOfWeek, getPlaceholder, getSideAndAlignFromAllPossibleConnectedPositions, handleCalendarInitialFocus, hasTime, injectDocument, injectIsClient, injectNgControl, injectWindow, isAfter, isAfterOrSame, isBefore, isBeforeOrSame, isBetween, isBetweenInclusive, isCalendarDateTime, isInsideForm, isNullish, isNumber, isZonedDateTime, j, k, n, p, provideValueAccessor, toDate, watch };
|
2103
|
+
export { A, ALT, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, ASTERISK, BACKSPACE, CAPS_LOCK, CONTROL, CTRL, DELETE, END, ENTER, ESCAPE, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, HOME, META, P, PAGE_DOWN, PAGE_UP, RDX_POSITIONING_DEFAULTS, RDX_POSITIONS, RdxAutoFocusDirective, RdxFocusInitialDirective, RdxPositionAlign, RdxPositionSide, SHIFT, SPACE, SPACE_CODE, TAB, WINDOW, _IdGenerator, a, areAllDaysBetweenValid, clamp, createContent, createFormatter, createMonth, createMonths, getActiveElement, getAllPossibleConnectedPositions, getArrowPositionParams, getContentPosition, getDaysBetween, getDaysInMonth, getDefaultDate, getLastFirstDayOfWeek, getNextLastDayOfWeek, getOptsByGranularity, getPlaceholder, getSegmentElements, getSideAndAlignFromAllPossibleConnectedPositions, handleCalendarInitialFocus, hasTime, initializeSegmentValues, injectDocument, injectIsClient, injectNgControl, injectWindow, isAcceptableSegmentKey, isAfter, isAfterOrSame, isBefore, isBeforeOrSame, isBetween, isBetweenInclusive, isCalendarDateTime, isInsideForm, isNullish, isNumber, isNumberString, isSegmentNavigationKey, isZonedDateTime, j, k, n, p, provideToken, provideValueAccessor, roundToStepPrecision, segmentBuilders, snapValueToStep, syncSegmentValues, syncTimeSegmentValues, toDate, useDateField, watch };
|
1015
2104
|
//# sourceMappingURL=radix-ng-primitives-core.mjs.map
|