@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 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;AAkatB,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;SAVmZ,MAAM,GAAG,SAAS;YAAU,iBAAiB;;cAA8C,cAAc,EAAE;mBAAiB,mBAAmB,EAAE,GAAG,SAAS;gBAAc,MAAM,GAAG,SAAS;gBAAc,iBAAiB;gBAAc,iBAAiB;;;;YAAwG,MAAM,GAAG,SAAS;eAAa,MAAM,GAAG,SAAS;eAAa,MAAM,GAAG,SAAS;gBAAc,MAAM,GAAG,SAAS;;eAAqC,8BAA8B,GAAG,SAAS;qBAAmB,2BAA2B,GAAG,SAAS;;;kBAUh6B,CAAC;AAC5E,KAAK,QAAQ,GAAG,YAAY,CAAC,OAAO,QAAQ,CAAC,CAAC;AAChD,eAAe,QAAQ,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"}
@@ -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
- return () => formElement?.removeEventListener('reset', handleFormReset);
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;AAynBtB,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"}
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
@@ -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
@@ -8,3 +8,4 @@ export * from './sortable-list/index.js';
8
8
  export * from './ordered-list/index.js';
9
9
  export * from './sortable-table/index.js';
10
10
  export { getUiMessages, resolveUiLanguage, uiLanguages } from './i18n.js';
11
+ export * from './segmented-date/index.js';
@@ -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,3 @@
1
+ export { default as SegmentedDateInput } from './SegmentedDateInput.svelte';
2
+ export * from './state.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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,2 @@
1
+ export { default as SegmentedDateInput } from './SegmentedDateInput.svelte';
2
+ export * from './state.js';
@@ -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
- "version": "0.4.0",
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",