@ibobbyts/svelte-ui-utils 0.4.1 → 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 +20 -0
- package/dist/dropdown/Dropdown.svelte +2 -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 +5 -0
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -845,3 +845,23 @@ bun add @ibobbyts/svelte-ui-utils@0.3.1 svelte
|
|
|
845
845
|
```
|
|
846
846
|
|
|
847
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。
|
|
@@ -563,10 +563,11 @@
|
|
|
563
563
|
{#if group.label}
|
|
564
564
|
<div class="suu-dropdown__group-label">{group.label}</div>
|
|
565
565
|
{/if}
|
|
566
|
-
{#each group.options as option}
|
|
566
|
+
{#each group.options as option, optionIndex}
|
|
567
567
|
<button
|
|
568
568
|
type="button"
|
|
569
569
|
class="suu-dropdown__option"
|
|
570
|
+
class:suu-dropdown__option--group-first={Boolean(group.label) && optionIndex === 0}
|
|
570
571
|
class:suu-dropdown__option--active={option.value === activeValue}
|
|
571
572
|
class:suu-dropdown__option--disabled={option.disabled}
|
|
572
573
|
role="option"
|
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
|
@@ -1283,6 +1283,7 @@
|
|
|
1283
1283
|
.suu-dropdown__panel {
|
|
1284
1284
|
max-height: var(--suu-dropdown-panel-max-height, 100vh);
|
|
1285
1285
|
overflow-y: auto;
|
|
1286
|
+
overflow-x: hidden;
|
|
1286
1287
|
background: var(--suu-color-bg);
|
|
1287
1288
|
overscroll-behavior: contain;
|
|
1288
1289
|
}
|
|
@@ -1313,6 +1314,10 @@
|
|
|
1313
1314
|
text-align: left;
|
|
1314
1315
|
}
|
|
1315
1316
|
|
|
1317
|
+
.suu-dropdown__option--group-first {
|
|
1318
|
+
border-top: 1px solid var(--suu-color-border);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1316
1321
|
.suu-dropdown--multiselect .suu-dropdown__option {
|
|
1317
1322
|
display: flex;
|
|
1318
1323
|
gap: 10px;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ibobbyts/svelte-ui-utils",
|
|
3
|
-
"version": "0.4.
|
|
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",
|