@ibobbyts/svelte-ui-utils 0.4.0 → 0.4.2
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 +32 -0
- package/dist/dropdown/Dropdown.svelte +104 -1
- package/dist/dropdown/Dropdown.svelte.d.ts +2 -0
- package/dist/dropdown/Dropdown.svelte.d.ts.map +1 -1
- package/dist/dropdown/types.d.ts +2 -0
- package/dist/dropdown/types.d.ts.map +1 -1
- package/dist/dropdown-search/DropdownSearch.svelte +18 -1
- package/dist/dropdown-search/DropdownSearch.svelte.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/segmented-date/SegmentedDateInput.svelte +193 -0
- package/dist/segmented-date/SegmentedDateInput.svelte.d.ts +26 -0
- package/dist/segmented-date/SegmentedDateInput.svelte.d.ts.map +1 -0
- package/dist/segmented-date/index.d.ts +3 -0
- package/dist/segmented-date/index.d.ts.map +1 -0
- package/dist/segmented-date/index.js +2 -0
- package/dist/segmented-date/state.d.ts +10 -0
- package/dist/segmented-date/state.d.ts.map +1 -0
- package/dist/segmented-date/state.js +67 -0
- package/dist/style.css +6 -0
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -393,6 +393,18 @@ use `menuAlign="right"` to align their right edges instead. `fitContent` sizes
|
|
|
393
393
|
the menu to its longest option while keeping the selected edge aligned.
|
|
394
394
|
`DataTable` uses this same component for its page-size picker.
|
|
395
395
|
|
|
396
|
+
For keyboard typeahead, an option may provide `searchText` separately from its
|
|
397
|
+
display `label`. Typeahead matches the beginning of `searchText` or `label`,
|
|
398
|
+
while the label remains the only text shown in the trigger and menu:
|
|
399
|
+
|
|
400
|
+
```svelte
|
|
401
|
+
<Dropdown
|
|
402
|
+
value="cn"
|
|
403
|
+
options={[{ label: '中国', searchText: 'China', value: 'cn' }]}
|
|
404
|
+
ariaLabel="Country"
|
|
405
|
+
/>
|
|
406
|
+
```
|
|
407
|
+
|
|
396
408
|
Set `multiselect={true}` to control the component with a `DropdownValue[]`.
|
|
397
409
|
Each option then shows a checkbox, selecting or deselecting an option keeps the
|
|
398
410
|
menu open, and `onChange` receives the complete selected-value array in option
|
|
@@ -833,3 +845,23 @@ bun add @ibobbyts/svelte-ui-utils@0.3.1 svelte
|
|
|
833
845
|
```
|
|
834
846
|
|
|
835
847
|
Use it after each release when a Bun-based project will consume the package.
|
|
848
|
+
|
|
849
|
+
## 分段日期输入(本地新增,尚未发布)
|
|
850
|
+
|
|
851
|
+
```svelte
|
|
852
|
+
<script lang="ts">
|
|
853
|
+
import { SegmentedDateInput } from '@ibobbyts/svelte-ui-utils/segmented-date';
|
|
854
|
+
let dob = $state('');
|
|
855
|
+
</script>
|
|
856
|
+
|
|
857
|
+
<SegmentedDateInput name="dob" bind:value={dob} ariaLabel="生日"
|
|
858
|
+
yearLabel="年" monthLabel="月" dayLabel="日" autocomplete="bday" />
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
支持 `precision="year" | "month" | "day"`(默认 `day`)、数字输入、全角数字归一化、完整日期粘贴及月日失焦补零。`value` 可双向绑定;`onvalueinput` 接收用户输入后的组合值。日历有效性和业务范围由调用方校验。
|
|
862
|
+
|
|
863
|
+
默认用隐藏字段提交组合值;`submitParts={true}` 改为提交 `${name}Year`、`${name}Month`、`${name}Day`(按精度决定字段)。支持 `required`、`readonly`、`disabled`、`id`(关联年输入框)和生日自动填充。`ariaLabel`、各段标签及占位符由调用方提供,以接入应用本地化。
|
|
864
|
+
|
|
865
|
+
引入公共 `style.css` 后可独立使用,无需 Tailwind 或 DaisyUI。`class` 定制容器;`inputClass` 替换默认输入框装饰样式,布局仍由组件负责。MMS 通过该参数保留 DaisyUI 外观。
|
|
866
|
+
|
|
867
|
+
纯函数和类型可从 `@ibobbyts/svelte-ui-utils/segmented-date/state` 引入:`splitSegmentedDate`、`composeSegmentedDate`、`parsePastedSegmentedDate`、`SegmentedDateParts`、`SegmentedDatePrecision`;此入口不加载 Svelte,适用于服务端和 Node。
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
export let fitViewport = true;
|
|
28
28
|
export let fitContent = true;
|
|
29
29
|
export let disabled = false;
|
|
30
|
+
export let name: string | undefined = undefined;
|
|
31
|
+
export let required = false;
|
|
30
32
|
export let width: string | undefined = undefined;
|
|
31
33
|
export let minWidth: string | undefined = undefined;
|
|
32
34
|
export let maxWidth: string | undefined = undefined;
|
|
@@ -51,6 +53,12 @@
|
|
|
51
53
|
let portalMenuWidth: string | undefined = undefined;
|
|
52
54
|
let removeOpenViewportListeners: (() => void) | undefined = undefined;
|
|
53
55
|
let removeOutsidePointerListener: (() => void) | undefined = undefined;
|
|
56
|
+
let typeaheadBuffer = '';
|
|
57
|
+
let typeaheadTimer: ReturnType<typeof setTimeout> | undefined;
|
|
58
|
+
let typeaheadGeneration = 0;
|
|
59
|
+
// Keep the buffer long enough for users to type multi-character aliases
|
|
60
|
+
// while the menu is rendering a large option list.
|
|
61
|
+
const typeaheadTimeoutMs = 1000;
|
|
54
62
|
|
|
55
63
|
$: resolvedOptions = optionGroups === undefined ? options : optionGroups.flatMap((group) => group.options);
|
|
56
64
|
$: selectedValues = normalizeSelectedValues(multiselect && Array.isArray(value) ? value : []);
|
|
@@ -131,6 +139,70 @@
|
|
|
131
139
|
activeValue = enabledOptions[nextIndex]?.value ?? activeValue;
|
|
132
140
|
}
|
|
133
141
|
|
|
142
|
+
function clearTypeaheadBuffer() {
|
|
143
|
+
typeaheadBuffer = '';
|
|
144
|
+
typeaheadGeneration += 1;
|
|
145
|
+
if (typeaheadTimer !== undefined) {
|
|
146
|
+
clearTimeout(typeaheadTimer);
|
|
147
|
+
typeaheadTimer = undefined;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function scheduleTypeaheadReset() {
|
|
152
|
+
const generation = ++typeaheadGeneration;
|
|
153
|
+
if (typeaheadTimer !== undefined) {
|
|
154
|
+
clearTimeout(typeaheadTimer);
|
|
155
|
+
}
|
|
156
|
+
typeaheadTimer = setTimeout(() => {
|
|
157
|
+
if (generation !== typeaheadGeneration) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
typeaheadBuffer = '';
|
|
161
|
+
typeaheadTimer = undefined;
|
|
162
|
+
}, typeaheadTimeoutMs);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function scrollActiveOptionIntoView() {
|
|
166
|
+
void tick().then(() => {
|
|
167
|
+
if (!open || typeof document === 'undefined') {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const activeElement = Array.from(
|
|
171
|
+
menuElement?.querySelectorAll<HTMLElement>('.suu-dropdown__option') ?? []
|
|
172
|
+
).find((element) => element.dataset.value === String(activeValue));
|
|
173
|
+
activeElement?.scrollIntoView?.({ block: 'nearest' });
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function handleTypeahead(key: string) {
|
|
178
|
+
const normalizedKey = key.startsWith('Key') && key.length === 4
|
|
179
|
+
? key.slice(3).toLocaleLowerCase()
|
|
180
|
+
: key.toLocaleLowerCase();
|
|
181
|
+
const nextBuffer = `${typeaheadBuffer}${normalizedKey}`;
|
|
182
|
+
const findMatch = (prefix: string) =>
|
|
183
|
+
resolvedOptions.find(
|
|
184
|
+
(option) =>
|
|
185
|
+
!option.disabled &&
|
|
186
|
+
[option.searchText, option.label]
|
|
187
|
+
.filter((text): text is string => text !== undefined)
|
|
188
|
+
.some((text) => text.toLocaleLowerCase().startsWith(prefix))
|
|
189
|
+
);
|
|
190
|
+
const match = findMatch(nextBuffer) ?? findMatch(normalizedKey);
|
|
191
|
+
|
|
192
|
+
typeaheadBuffer = match ? (findMatch(nextBuffer) ? nextBuffer : normalizedKey) : nextBuffer;
|
|
193
|
+
scheduleTypeaheadReset();
|
|
194
|
+
if (!match) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!open) {
|
|
199
|
+
updateResolvedPlacement();
|
|
200
|
+
open = true;
|
|
201
|
+
}
|
|
202
|
+
activeValue = match.value;
|
|
203
|
+
scrollActiveOptionIntoView();
|
|
204
|
+
}
|
|
205
|
+
|
|
134
206
|
function selectOption(option: DropdownOption) {
|
|
135
207
|
if (option.disabled) {
|
|
136
208
|
return;
|
|
@@ -144,11 +216,13 @@
|
|
|
144
216
|
nextSelected.add(option.value);
|
|
145
217
|
}
|
|
146
218
|
const nextValues = normalizeSelectedValues([...nextSelected]);
|
|
219
|
+
clearTypeaheadBuffer();
|
|
147
220
|
void (onChange as DropdownMultiChangeHandler | undefined)?.(nextValues);
|
|
148
221
|
return;
|
|
149
222
|
}
|
|
150
223
|
|
|
151
224
|
open = false;
|
|
225
|
+
clearTypeaheadBuffer();
|
|
152
226
|
void (onChange as DropdownChangeHandler | undefined)?.(option.value);
|
|
153
227
|
buttonElement?.focus();
|
|
154
228
|
}
|
|
@@ -160,6 +234,7 @@
|
|
|
160
234
|
if (!open) {
|
|
161
235
|
updateResolvedPlacement();
|
|
162
236
|
}
|
|
237
|
+
clearTypeaheadBuffer();
|
|
163
238
|
open = !open;
|
|
164
239
|
activeValue = initialActiveValue();
|
|
165
240
|
}
|
|
@@ -179,6 +254,7 @@
|
|
|
179
254
|
event.preventDefault();
|
|
180
255
|
open = false;
|
|
181
256
|
}
|
|
257
|
+
clearTypeaheadBuffer();
|
|
182
258
|
return;
|
|
183
259
|
}
|
|
184
260
|
|
|
@@ -193,6 +269,18 @@
|
|
|
193
269
|
return;
|
|
194
270
|
}
|
|
195
271
|
|
|
272
|
+
if (
|
|
273
|
+
((event.key.length === 1 && event.key !== ' ') ||
|
|
274
|
+
(event.key.startsWith('Key') && event.key.length === 4)) &&
|
|
275
|
+
!event.ctrlKey &&
|
|
276
|
+
!event.metaKey &&
|
|
277
|
+
!event.altKey
|
|
278
|
+
) {
|
|
279
|
+
event.preventDefault();
|
|
280
|
+
handleTypeahead(event.key);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
196
284
|
if ((event.key === 'Enter' || event.key === ' ') && open) {
|
|
197
285
|
event.preventDefault();
|
|
198
286
|
const index = activeOptionIndex(activeValue);
|
|
@@ -366,6 +454,7 @@
|
|
|
366
454
|
onDestroy(() => {
|
|
367
455
|
disableOpenViewportTracking();
|
|
368
456
|
disableOutsidePointerDismissal();
|
|
457
|
+
clearTypeaheadBuffer();
|
|
369
458
|
});
|
|
370
459
|
</script>
|
|
371
460
|
|
|
@@ -402,6 +491,19 @@
|
|
|
402
491
|
<span class="suu-dropdown__chevron" aria-hidden="true"></span>
|
|
403
492
|
</button>
|
|
404
493
|
|
|
494
|
+
{#if name !== undefined && !multiselect}
|
|
495
|
+
<input
|
|
496
|
+
class="suu-visually-hidden"
|
|
497
|
+
type="text"
|
|
498
|
+
name={name}
|
|
499
|
+
value={String(value)}
|
|
500
|
+
required={required}
|
|
501
|
+
disabled={disabled}
|
|
502
|
+
tabindex="-1"
|
|
503
|
+
aria-label={ariaLabel}
|
|
504
|
+
/>
|
|
505
|
+
{/if}
|
|
506
|
+
|
|
405
507
|
{#if open}
|
|
406
508
|
<div
|
|
407
509
|
bind:this={menuElement}
|
|
@@ -461,10 +563,11 @@
|
|
|
461
563
|
{#if group.label}
|
|
462
564
|
<div class="suu-dropdown__group-label">{group.label}</div>
|
|
463
565
|
{/if}
|
|
464
|
-
{#each group.options as option}
|
|
566
|
+
{#each group.options as option, optionIndex}
|
|
465
567
|
<button
|
|
466
568
|
type="button"
|
|
467
569
|
class="suu-dropdown__option"
|
|
570
|
+
class:suu-dropdown__option--group-first={Boolean(group.label) && optionIndex === 0}
|
|
468
571
|
class:suu-dropdown__option--active={option.value === activeValue}
|
|
469
572
|
class:suu-dropdown__option--disabled={option.disabled}
|
|
470
573
|
role="option"
|
|
@@ -24,6 +24,8 @@ declare const Dropdown: $$__sveltets_2_IsomorphicComponent<{
|
|
|
24
24
|
fitViewport?: boolean;
|
|
25
25
|
fitContent?: boolean;
|
|
26
26
|
disabled?: boolean;
|
|
27
|
+
name?: string | undefined;
|
|
28
|
+
required?: boolean;
|
|
27
29
|
width?: string | undefined;
|
|
28
30
|
minWidth?: string | undefined;
|
|
29
31
|
maxWidth?: string | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dropdown.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/dropdown/Dropdown.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAER,iBAAiB,EAGjB,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,8BAA8B,EAC9B,iBAAiB,EACjB,2BAA2B,EAE5B,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"Dropdown.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/dropdown/Dropdown.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAER,iBAAiB,EAGjB,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,8BAA8B,EAC9B,iBAAiB,EACjB,2BAA2B,EAE5B,MAAM,YAAY,CAAC;AA+ftB,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IAC9G,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,QAAQ;SAVqb,MAAM,GAAG,SAAS;YAAU,iBAAiB;;cAA8C,cAAc,EAAE;mBAAiB,mBAAmB,EAAE,GAAG,SAAS;gBAAc,MAAM,GAAG,SAAS;gBAAc,iBAAiB;gBAAc,iBAAiB;;;;WAAuG,MAAM,GAAG,SAAS;;YAAsC,MAAM,GAAG,SAAS;eAAa,MAAM,GAAG,SAAS;eAAa,MAAM,GAAG,SAAS;gBAAc,MAAM,GAAG,SAAS;;eAAqC,8BAA8B,GAAG,SAAS;qBAAmB,2BAA2B,GAAG,SAAS;;;kBAUz/B,CAAC;AAC5E,KAAK,QAAQ,GAAG,YAAY,CAAC,OAAO,QAAQ,CAAC,CAAC;AAChD,eAAe,QAAQ,CAAC"}
|
package/dist/dropdown/types.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export type DropdownPlacement = 'auto' | 'up' | 'down';
|
|
|
5
5
|
export type DropdownMenuAlign = 'left' | 'right';
|
|
6
6
|
export interface DropdownOption {
|
|
7
7
|
label: string;
|
|
8
|
+
/** Optional text used for keyboard typeahead matching instead of (or in addition to) the label. */
|
|
9
|
+
searchText?: string;
|
|
8
10
|
value: DropdownValue;
|
|
9
11
|
disabled?: boolean;
|
|
10
12
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/dropdown/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,MAAM,MAAM,kBAAkB,GAAG,aAAa,EAAE,CAAC;AAEjD,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG,kBAAkB,CAAC;AAEnE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC;AAEvD,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,OAAO,CAAC;AAEjD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAED,MAAM,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnF,MAAM,MAAM,0BAA0B,GAAG,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE7F,MAAM,MAAM,8BAA8B,GAAG;IAC5C,cAAc,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/D,CAAC,gBAAgB,CAAC,CAAC;AAEpB,MAAM,MAAM,2BAA2B,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/dropdown/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,MAAM,MAAM,kBAAkB,GAAG,aAAa,EAAE,CAAC;AAEjD,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG,kBAAkB,CAAC;AAEnE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC;AAEvD,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,OAAO,CAAC;AAEjD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,mGAAmG;IACnG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAED,MAAM,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnF,MAAM,MAAM,0BAA0B,GAAG,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE7F,MAAM,MAAM,8BAA8B,GAAG;IAC5C,cAAc,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/D,CAAC,gBAAgB,CAAC,CAAC;AAEpB,MAAM,MAAM,2BAA2B,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC"}
|
|
@@ -561,7 +561,24 @@
|
|
|
561
561
|
if (searchOnExternalValueChange && normalizeDropdownSearchValue(value)) {
|
|
562
562
|
handleExternalValue(value);
|
|
563
563
|
}
|
|
564
|
-
|
|
564
|
+
|
|
565
|
+
const visibilityObserver =
|
|
566
|
+
typeof IntersectionObserver === 'undefined'
|
|
567
|
+
? undefined
|
|
568
|
+
: new IntersectionObserver(([entry]) => {
|
|
569
|
+
if (!entry?.isIntersecting) {
|
|
570
|
+
focused = false;
|
|
571
|
+
showingFocusOptions = false;
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
if (inputElement && visibilityObserver) {
|
|
575
|
+
visibilityObserver.observe(inputElement);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return () => {
|
|
579
|
+
formElement?.removeEventListener('reset', handleFormReset);
|
|
580
|
+
visibilityObserver?.disconnect();
|
|
581
|
+
};
|
|
565
582
|
});
|
|
566
583
|
</script>
|
|
567
584
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownSearch.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/dropdown-search/DropdownSearch.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAM5D,OAAO,KAAK,EACR,0BAA0B,EAC1B,yBAAyB,EACzB,kBAAkB,EAClB,6BAA6B,EAC7B,yBAAyB,EACzB,qCAAqC,EACrC,wCAAwC,EACxC,oBAAoB,EACrB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"DropdownSearch.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/dropdown-search/DropdownSearch.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAM5D,OAAO,KAAK,EACR,0BAA0B,EAC1B,yBAAyB,EACzB,kBAAkB,EAClB,6BAA6B,EAC7B,yBAAyB,EACzB,qCAAqC,EACrC,wCAAwC,EACxC,oBAAoB,EACrB,MAAM,YAAY,CAAC;AA0oBtB,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IAC9G,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,cAAc;;mBAdihC,kBAAkB,GAAG,IAAI;oBAAkB,kBAAkB,EAAE;aAAW,oBAAoB;;;;;;;;;mBAAyR,kBAAkB,EAAE;iBAAe,MAAM,GAAG,SAAS;iBAAe,yBAAyB;SAAO,MAAM,GAAG,SAAS;WAAS,MAAM,GAAG,SAAS;gBAAc,MAAM,GAAG,SAAS;gBAAc,MAAM,GAAG,SAAS;;eAAyC,UAAU;oBAAkB,MAAM,GAAG,SAAS;kBAAgB,MAAM,GAAG,SAAS;iBAAe,MAAM,GAAG,SAAS;;YAA4E,MAAM,GAAG,SAAS;eAAa,MAAM,GAAG,SAAS;eAAa,MAAM,GAAG,SAAS;;8BAA8E,qCAAqC,GAChhE,SAAS;mBAAiB,6BAA6B;eAAe,CAAC,CAAC,MAAM,EAAE,0BAA0B,KAAK,IAAI,CAAC,GACpH,SAAS;eAAa,CAAC,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC,GAAG,SAAS;iBAAe,CAAC,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC,GAAG,SAAS;4BAA4B,wCAAwC,GACzM,SAAS;qBAAqB,CAAC,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC,GACtE,SAAS;kBAAgB,CAAC,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC,GAAG,SAAS;cAAY,CAAC,CAAC,MAAM,EAAE,yBAAyB,KAAK,IAAI,CAAC,GAAG,SAAS;;;;;kBAU1C,CAAC;AAClF,KAAK,cAAc,GAAG,YAAY,CAAC,OAAO,cAAc,CAAC,CAAC;AAC5D,eAAe,cAAc,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,4 +9,5 @@ export * from './ordered-list/index.js';
|
|
|
9
9
|
export * from './sortable-table/index.js';
|
|
10
10
|
export { getUiMessages, resolveUiLanguage, uiLanguages } from './i18n.js';
|
|
11
11
|
export type { UiLanguage, UiMessages } from './i18n.js';
|
|
12
|
+
export * from './segmented-date/index.js';
|
|
12
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC1E,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC1E,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACxD,cAAc,2BAA2B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { untrack } from "svelte";
|
|
3
|
+
import {
|
|
4
|
+
composeSegmentedDate,
|
|
5
|
+
parsePastedSegmentedDate,
|
|
6
|
+
splitSegmentedDate,
|
|
7
|
+
type SegmentedDatePrecision,
|
|
8
|
+
} from "./state.js";
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
value = $bindable(""),
|
|
12
|
+
precision = "day",
|
|
13
|
+
name,
|
|
14
|
+
id,
|
|
15
|
+
required = false,
|
|
16
|
+
readonly = false,
|
|
17
|
+
disabled = false,
|
|
18
|
+
ariaLabel,
|
|
19
|
+
yearLabel,
|
|
20
|
+
monthLabel,
|
|
21
|
+
dayLabel,
|
|
22
|
+
yearPlaceholder = "YYYY",
|
|
23
|
+
monthPlaceholder = "MM",
|
|
24
|
+
dayPlaceholder = "DD",
|
|
25
|
+
submitParts = false,
|
|
26
|
+
autocomplete,
|
|
27
|
+
class: className = "",
|
|
28
|
+
inputClass = "",
|
|
29
|
+
onvalueinput,
|
|
30
|
+
} = $props<{
|
|
31
|
+
value?: string;
|
|
32
|
+
precision?: SegmentedDatePrecision;
|
|
33
|
+
name: string;
|
|
34
|
+
id?: string;
|
|
35
|
+
required?: boolean;
|
|
36
|
+
readonly?: boolean;
|
|
37
|
+
disabled?: boolean;
|
|
38
|
+
ariaLabel: string;
|
|
39
|
+
yearLabel: string;
|
|
40
|
+
monthLabel?: string;
|
|
41
|
+
dayLabel?: string;
|
|
42
|
+
yearPlaceholder?: string;
|
|
43
|
+
monthPlaceholder?: string;
|
|
44
|
+
dayPlaceholder?: string;
|
|
45
|
+
submitParts?: boolean;
|
|
46
|
+
autocomplete?: "bday";
|
|
47
|
+
class?: string;
|
|
48
|
+
inputClass?: string;
|
|
49
|
+
onvalueinput?: (value: string) => void;
|
|
50
|
+
}>();
|
|
51
|
+
|
|
52
|
+
let year = $state("");
|
|
53
|
+
let month = $state("");
|
|
54
|
+
let day = $state("");
|
|
55
|
+
|
|
56
|
+
$effect(() => {
|
|
57
|
+
const parts = splitSegmentedDate(value, precision);
|
|
58
|
+
// Keep partially entered segments while the child is emitting an incomplete value.
|
|
59
|
+
const current = untrack(() => composeSegmentedDate({ year, month, day }, precision));
|
|
60
|
+
if (value !== current) {
|
|
61
|
+
year = parts.year;
|
|
62
|
+
month = parts.month;
|
|
63
|
+
day = parts.day;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
function updateValue() {
|
|
68
|
+
const nextValue = composeSegmentedDate({ year, month, day }, precision);
|
|
69
|
+
if (nextValue === value) return;
|
|
70
|
+
value = nextValue;
|
|
71
|
+
onvalueinput?.(value);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function handleInput(part: SegmentedDatePrecision, event: Event) {
|
|
75
|
+
const input = event.currentTarget as HTMLInputElement;
|
|
76
|
+
const normalized = input.value.normalize("NFKC").replace(/\D/g, "")
|
|
77
|
+
.slice(0, part === "year" ? 4 : 2);
|
|
78
|
+
input.value = normalized;
|
|
79
|
+
if (part === "year") year = normalized;
|
|
80
|
+
if (part === "month") month = normalized;
|
|
81
|
+
if (part === "day") day = normalized;
|
|
82
|
+
updateValue();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function handleBlur(part: "month" | "day") {
|
|
86
|
+
if (part === "month" && /^\d$/.test(month)) month = month.padStart(2, "0");
|
|
87
|
+
if (part === "day" && /^\d$/.test(day)) day = day.padStart(2, "0");
|
|
88
|
+
updateValue();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function handlePaste(event: ClipboardEvent) {
|
|
92
|
+
const parts = parsePastedSegmentedDate(
|
|
93
|
+
event.clipboardData?.getData("text") ?? "",
|
|
94
|
+
precision,
|
|
95
|
+
);
|
|
96
|
+
if (!parts) return;
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
year = parts.year;
|
|
99
|
+
month = parts.month;
|
|
100
|
+
day = parts.day;
|
|
101
|
+
updateValue();
|
|
102
|
+
}
|
|
103
|
+
</script>
|
|
104
|
+
|
|
105
|
+
{#if !submitParts}
|
|
106
|
+
<input type="hidden" {name} {value} {disabled} />
|
|
107
|
+
{/if}
|
|
108
|
+
|
|
109
|
+
<div
|
|
110
|
+
class={["suu-segmented-date", className]}
|
|
111
|
+
data-precision={precision}
|
|
112
|
+
role="group"
|
|
113
|
+
aria-label={ariaLabel}
|
|
114
|
+
>
|
|
115
|
+
<input
|
|
116
|
+
class={["suu-segmented-date__input", inputClass]}
|
|
117
|
+
class:suu-segmented-date__input--default={!inputClass}
|
|
118
|
+
type="text"
|
|
119
|
+
name={submitParts ? `${name}Year` : undefined}
|
|
120
|
+
{id}
|
|
121
|
+
bind:value={year}
|
|
122
|
+
oninput={(event) => handleInput("year", event)}
|
|
123
|
+
onpaste={handlePaste}
|
|
124
|
+
inputmode="numeric"
|
|
125
|
+
pattern="[0-9][0-9][0-9][0-9]"
|
|
126
|
+
maxlength="4"
|
|
127
|
+
placeholder={yearPlaceholder}
|
|
128
|
+
aria-label={yearLabel}
|
|
129
|
+
autocomplete={autocomplete ? "bday-year" : undefined}
|
|
130
|
+
{required}
|
|
131
|
+
{readonly}
|
|
132
|
+
{disabled}
|
|
133
|
+
/>
|
|
134
|
+
{#if precision !== "year"}
|
|
135
|
+
<input
|
|
136
|
+
class={["suu-segmented-date__input", inputClass]}
|
|
137
|
+
class:suu-segmented-date__input--default={!inputClass}
|
|
138
|
+
type="text"
|
|
139
|
+
name={submitParts ? `${name}Month` : undefined}
|
|
140
|
+
bind:value={month}
|
|
141
|
+
oninput={(event) => handleInput("month", event)}
|
|
142
|
+
onblur={() => handleBlur("month")}
|
|
143
|
+
onpaste={handlePaste}
|
|
144
|
+
inputmode="numeric"
|
|
145
|
+
pattern="[0-9][0-9]?"
|
|
146
|
+
maxlength="2"
|
|
147
|
+
placeholder={monthPlaceholder}
|
|
148
|
+
aria-label={monthLabel}
|
|
149
|
+
autocomplete={autocomplete ? "bday-month" : undefined}
|
|
150
|
+
{required}
|
|
151
|
+
{readonly}
|
|
152
|
+
{disabled}
|
|
153
|
+
/>
|
|
154
|
+
{/if}
|
|
155
|
+
{#if precision === "day"}
|
|
156
|
+
<input
|
|
157
|
+
class={["suu-segmented-date__input", inputClass]}
|
|
158
|
+
class:suu-segmented-date__input--default={!inputClass}
|
|
159
|
+
type="text"
|
|
160
|
+
name={submitParts ? `${name}Day` : undefined}
|
|
161
|
+
bind:value={day}
|
|
162
|
+
oninput={(event) => handleInput("day", event)}
|
|
163
|
+
onblur={() => handleBlur("day")}
|
|
164
|
+
onpaste={handlePaste}
|
|
165
|
+
inputmode="numeric"
|
|
166
|
+
pattern="[0-9][0-9]?"
|
|
167
|
+
maxlength="2"
|
|
168
|
+
placeholder={dayPlaceholder}
|
|
169
|
+
aria-label={dayLabel}
|
|
170
|
+
autocomplete={autocomplete ? "bday-day" : undefined}
|
|
171
|
+
{required}
|
|
172
|
+
{readonly}
|
|
173
|
+
{disabled}
|
|
174
|
+
/>
|
|
175
|
+
{/if}
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
<style>
|
|
179
|
+
.suu-segmented-date { display: grid; min-width: 0; gap: 0.5rem; }
|
|
180
|
+
.suu-segmented-date[data-precision="year"] { grid-template-columns: minmax(0, 1fr); }
|
|
181
|
+
.suu-segmented-date[data-precision="month"] { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
|
|
182
|
+
.suu-segmented-date[data-precision="day"] { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr); }
|
|
183
|
+
.suu-segmented-date__input { min-width: 0; width: 100%; box-sizing: border-box; }
|
|
184
|
+
.suu-segmented-date__input--default {
|
|
185
|
+
padding: 0.5rem 0.75rem;
|
|
186
|
+
border: 1px solid var(--suu-color-border);
|
|
187
|
+
border-radius: var(--suu-radius);
|
|
188
|
+
background: var(--suu-color-bg);
|
|
189
|
+
color: var(--suu-color-text);
|
|
190
|
+
font: inherit;
|
|
191
|
+
}
|
|
192
|
+
.suu-segmented-date__input--default:focus-visible { outline: 2px solid var(--suu-color-accent); outline-offset: 2px; }
|
|
193
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type SegmentedDatePrecision } from "./state.js";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
value?: string;
|
|
4
|
+
precision?: SegmentedDatePrecision;
|
|
5
|
+
name: string;
|
|
6
|
+
id?: string;
|
|
7
|
+
required?: boolean;
|
|
8
|
+
readonly?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
ariaLabel: string;
|
|
11
|
+
yearLabel: string;
|
|
12
|
+
monthLabel?: string;
|
|
13
|
+
dayLabel?: string;
|
|
14
|
+
yearPlaceholder?: string;
|
|
15
|
+
monthPlaceholder?: string;
|
|
16
|
+
dayPlaceholder?: string;
|
|
17
|
+
submitParts?: boolean;
|
|
18
|
+
autocomplete?: "bday";
|
|
19
|
+
class?: string;
|
|
20
|
+
inputClass?: string;
|
|
21
|
+
onvalueinput?: (value: string) => void;
|
|
22
|
+
};
|
|
23
|
+
declare const SegmentedDateInput: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
24
|
+
type SegmentedDateInput = ReturnType<typeof SegmentedDateInput>;
|
|
25
|
+
export default SegmentedDateInput;
|
|
26
|
+
//# sourceMappingURL=SegmentedDateInput.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SegmentedDateInput.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/segmented-date/SegmentedDateInput.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,EAIC,KAAK,sBAAsB,EAC9B,MAAM,YAAY,CAAC;AAEvB,KAAK,gBAAgB,GAAG;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C,CAAC;AAkGN,QAAA,MAAM,kBAAkB,2DAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/segmented-date/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type SegmentedDatePrecision = "year" | "month" | "day";
|
|
2
|
+
export type SegmentedDateParts = {
|
|
3
|
+
year: string;
|
|
4
|
+
month: string;
|
|
5
|
+
day: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function splitSegmentedDate(value: string, precision: SegmentedDatePrecision): SegmentedDateParts;
|
|
8
|
+
export declare function composeSegmentedDate(parts: SegmentedDateParts, precision: SegmentedDatePrecision): string;
|
|
9
|
+
export declare function parsePastedSegmentedDate(value: string, precision: SegmentedDatePrecision): SegmentedDateParts | null;
|
|
10
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/lib/segmented-date/state.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACf,CAAC;AAQF,wBAAgB,kBAAkB,CAC9B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,sBAAsB,GAClC,kBAAkB,CAuBpB;AAED,wBAAgB,oBAAoB,CAChC,KAAK,EAAE,kBAAkB,EACzB,SAAS,EAAE,sBAAsB,GAClC,MAAM,CAqBR;AAED,wBAAgB,wBAAwB,CACpC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,sBAAsB,GAClC,kBAAkB,GAAG,IAAI,CAe3B"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const EMPTY_PARTS = { year: "", month: "", day: "" };
|
|
2
|
+
function normalizePart(value) {
|
|
3
|
+
return value.normalize("NFKC").trim();
|
|
4
|
+
}
|
|
5
|
+
export function splitSegmentedDate(value, precision) {
|
|
6
|
+
const normalized = value.normalize("NFKC").trim();
|
|
7
|
+
const patterns = {
|
|
8
|
+
year: /^([^-]*)$/,
|
|
9
|
+
month: /^([^-]*)-([^-]*)$/,
|
|
10
|
+
day: /^([^-]*)-([^-]*)-([^-]*)$/,
|
|
11
|
+
};
|
|
12
|
+
const match = normalized.match(patterns[precision]);
|
|
13
|
+
if (!match) {
|
|
14
|
+
if (precision === "month" && /^\d{4}$/.test(normalized)) {
|
|
15
|
+
return { year: normalized, month: "", day: "" };
|
|
16
|
+
}
|
|
17
|
+
if (precision === "month" && /^\d{1,2}$/.test(normalized)) {
|
|
18
|
+
return { year: "", month: normalized, day: "" };
|
|
19
|
+
}
|
|
20
|
+
return { ...EMPTY_PARTS };
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
year: match[1] ?? "",
|
|
24
|
+
month: precision === "year" ? "" : (match[2] ?? ""),
|
|
25
|
+
day: precision === "day" ? (match[3] ?? "") : "",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function composeSegmentedDate(parts, precision) {
|
|
29
|
+
const year = normalizePart(parts.year);
|
|
30
|
+
const month = normalizePart(parts.month);
|
|
31
|
+
const day = normalizePart(parts.day);
|
|
32
|
+
if (!year && (precision === "year" || !month) && (precision !== "day" || !day)) {
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
if (precision === "year")
|
|
36
|
+
return year;
|
|
37
|
+
if (precision === "month") {
|
|
38
|
+
if (/^\d{4}$/.test(year) && /^\d{1,2}$/.test(month)) {
|
|
39
|
+
return `${year}-${month.padStart(2, "0")}`;
|
|
40
|
+
}
|
|
41
|
+
if (/^\d{4}$/.test(year) && !month)
|
|
42
|
+
return year;
|
|
43
|
+
if (!year && /^\d{1,2}$/.test(month))
|
|
44
|
+
return month.padStart(2, "0");
|
|
45
|
+
return `${year}-${month}`;
|
|
46
|
+
}
|
|
47
|
+
if (/^\d{4}$/.test(year) && /^\d{1,2}$/.test(month) && /^\d{1,2}$/.test(day)) {
|
|
48
|
+
return `${year}-${month.padStart(2, "0")}-${day.padStart(2, "0")}`;
|
|
49
|
+
}
|
|
50
|
+
return `${year}-${month}-${day}`;
|
|
51
|
+
}
|
|
52
|
+
export function parsePastedSegmentedDate(value, precision) {
|
|
53
|
+
const normalized = value.normalize("NFKC").trim();
|
|
54
|
+
const patterns = {
|
|
55
|
+
year: /^(\d{4})$/,
|
|
56
|
+
month: /^(?:(\d{4})[-/.](\d{1,2})|(\d{1,2}))$/,
|
|
57
|
+
day: /^(\d{4})[-/.](\d{1,2})[-/.](\d{1,2})$/,
|
|
58
|
+
};
|
|
59
|
+
const match = normalized.match(patterns[precision]);
|
|
60
|
+
if (!match)
|
|
61
|
+
return null;
|
|
62
|
+
return {
|
|
63
|
+
year: match[1] ?? "",
|
|
64
|
+
month: precision === "year" ? "" : (match[2] ?? match[3]).padStart(2, "0"),
|
|
65
|
+
day: precision === "day" ? match[3].padStart(2, "0") : "",
|
|
66
|
+
};
|
|
67
|
+
}
|
package/dist/style.css
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
.suu-visually-hidden {
|
|
43
43
|
position: absolute;
|
|
44
|
+
pointer-events: none;
|
|
44
45
|
overflow: hidden;
|
|
45
46
|
width: 1px;
|
|
46
47
|
height: 1px;
|
|
@@ -1282,6 +1283,7 @@
|
|
|
1282
1283
|
.suu-dropdown__panel {
|
|
1283
1284
|
max-height: var(--suu-dropdown-panel-max-height, 100vh);
|
|
1284
1285
|
overflow-y: auto;
|
|
1286
|
+
overflow-x: hidden;
|
|
1285
1287
|
background: var(--suu-color-bg);
|
|
1286
1288
|
overscroll-behavior: contain;
|
|
1287
1289
|
}
|
|
@@ -1312,6 +1314,10 @@
|
|
|
1312
1314
|
text-align: left;
|
|
1313
1315
|
}
|
|
1314
1316
|
|
|
1317
|
+
.suu-dropdown__option--group-first {
|
|
1318
|
+
border-top: 1px solid var(--suu-color-border);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1315
1321
|
.suu-dropdown--multiselect .suu-dropdown__option {
|
|
1316
1322
|
display: flex;
|
|
1317
1323
|
gap: 10px;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ibobbyts/svelte-ui-utils",
|
|
3
|
-
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Reusable Svelte 5 UI utilities for dialogs, toast notifications, dropdown search, and data tables.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -77,7 +77,16 @@
|
|
|
77
77
|
"svelte": "./dist/sortable-table/index.js",
|
|
78
78
|
"default": "./dist/sortable-table/index.js"
|
|
79
79
|
},
|
|
80
|
-
"./style.css": "./dist/style.css"
|
|
80
|
+
"./style.css": "./dist/style.css",
|
|
81
|
+
"./segmented-date": {
|
|
82
|
+
"types": "./dist/segmented-date/index.d.ts",
|
|
83
|
+
"svelte": "./dist/segmented-date/index.js",
|
|
84
|
+
"default": "./dist/segmented-date/index.js"
|
|
85
|
+
},
|
|
86
|
+
"./segmented-date/state": {
|
|
87
|
+
"types": "./dist/segmented-date/state.d.ts",
|
|
88
|
+
"default": "./dist/segmented-date/state.js"
|
|
89
|
+
}
|
|
81
90
|
},
|
|
82
91
|
"publishConfig": {
|
|
83
92
|
"access": "public",
|