bits-ui 2.17.1 → 2.17.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.
@@ -11,6 +11,7 @@ import { createBitsAttrs, boolToStr, boolToEmptyStrOrUndef } from "../../interna
11
11
  import { kbd } from "../../internal/kbd.js";
12
12
  import { isElementOrSVGElement } from "../../internal/is.js";
13
13
  import { isValidIndex } from "../../internal/arrays.js";
14
+ import { SvelteResizeObserver } from "../../internal/svelte-resize-observer.svelte.js";
14
15
  import { linearScale } from "../../internal/math.js";
15
16
  const sliderAttrs = createBitsAttrs({
16
17
  component: "slider",
@@ -21,6 +22,7 @@ class SliderBaseRootState {
21
22
  opts;
22
23
  attachment;
23
24
  isActive = $state(false);
25
+ #layoutVersion = $state(0);
24
26
  direction = $derived.by(() => {
25
27
  if (this.opts.orientation.current === "horizontal") {
26
28
  return this.opts.dir.current === "rtl" ? "rl" : "lr";
@@ -38,7 +40,11 @@ class SliderBaseRootState {
38
40
  this.opts = opts;
39
41
  this.attachment = attachRef(opts.ref);
40
42
  this.domContext = new DOMContext(this.opts.ref);
43
+ new SvelteResizeObserver(() => this.opts.ref.current, this.#handleLayoutChange);
41
44
  }
45
+ #handleLayoutChange = () => {
46
+ this.#layoutVersion += 1;
47
+ };
42
48
  isThumbActive(_index) {
43
49
  return this.isActive;
44
50
  }
@@ -54,6 +60,7 @@ class SliderBaseRootState {
54
60
  return Array.from(node.querySelectorAll(sliderAttrs.selector("thumb")));
55
61
  };
56
62
  getThumbScale = () => {
63
+ void this.#layoutVersion;
57
64
  // If trackPadding is explicitly set, use it directly instead of calculating from thumb size
58
65
  const trackPadding = this.opts.trackPadding?.current;
59
66
  if (trackPadding !== undefined && trackPadding > 0) {
@@ -64,6 +64,7 @@ export declare class TimeFieldRootState<T extends TimeValue = Time> {
64
64
  descriptionNode: HTMLElement | null;
65
65
  validationNode: HTMLElement | null;
66
66
  states: import("../../shared/date/types.js").TimeSegmentStateMap;
67
+ hourInputDayPeriodHint: TimeSegmentObj["dayPeriod"];
67
68
  dayPeriodNode: HTMLElement | null;
68
69
  name: string;
69
70
  readonly maxValueTime: Time | undefined;
@@ -47,6 +47,15 @@ const SEGMENT_CONFIGS = {
47
47
  padZero: true,
48
48
  },
49
49
  };
50
+ function get24HourValueFromTypedHour(hour, dayPeriod) {
51
+ const parsedHour = Number.parseInt(hour);
52
+ if (Number.isNaN(parsedHour))
53
+ return hour;
54
+ if (dayPeriod === "AM") {
55
+ return parsedHour === 12 ? "0" : `${parsedHour}`;
56
+ }
57
+ return parsedHour < 12 ? `${parsedHour + 12}` : `${parsedHour}`;
58
+ }
50
59
  export class TimeFieldRootState {
51
60
  static create(opts, rangeRoot) {
52
61
  return TimeFieldRootContext.set(new TimeFieldRootState(opts, rangeRoot));
@@ -79,6 +88,7 @@ export class TimeFieldRootState {
79
88
  descriptionNode = $state(null);
80
89
  validationNode = $state(null);
81
90
  states = initTimeSegmentStates();
91
+ hourInputDayPeriodHint = null;
82
92
  dayPeriodNode = $state(null);
83
93
  name = $state("");
84
94
  maxValueTime = $derived.by(() => {
@@ -389,9 +399,14 @@ export class TimeFieldRootState {
389
399
  const next = cb(prev[part]);
390
400
  this.states.hour.updating = next;
391
401
  if (next !== null && prev.dayPeriod !== null) {
392
- const dayPeriod = this.formatter.dayPeriod(toDate(this.#toDateValue(this.timeRef.set({ hour: Number.parseInt(next) }))), this.hourCycle);
393
- if (dayPeriod === "AM" || dayPeriod === "PM") {
394
- prev.dayPeriod = dayPeriod;
402
+ if (this.hourCycle !== 24 && this.hourInputDayPeriodHint !== null) {
403
+ prev.dayPeriod = this.hourInputDayPeriodHint;
404
+ }
405
+ else {
406
+ const dayPeriod = this.formatter.dayPeriod(toDate(this.#toDateValue(this.timeRef.set({ hour: Number.parseInt(next) }))), this.hourCycle);
407
+ if (dayPeriod === "AM" || dayPeriod === "PM") {
408
+ prev.dayPeriod = dayPeriod;
409
+ }
395
410
  }
396
411
  }
397
412
  newSegmentValues = { ...prev, [part]: next };
@@ -407,17 +422,22 @@ export class TimeFieldRootState {
407
422
  newSegmentValues = { ...prev, [part]: next };
408
423
  }
409
424
  this.segmentValues = newSegmentValues;
425
+ const segmentObjForValue = part === "hour" &&
426
+ this.hourCycle !== 24 &&
427
+ this.hourInputDayPeriodHint !== null &&
428
+ newSegmentValues.hour !== null
429
+ ? {
430
+ ...newSegmentValues,
431
+ hour: get24HourValueFromTypedHour(newSegmentValues.hour, this.hourInputDayPeriodHint),
432
+ }
433
+ : newSegmentValues;
410
434
  if (areAllTimeSegmentsFilled(newSegmentValues, this.#fieldNode)) {
411
435
  this.setValue(getTimeValueFromSegments({
412
- segmentObj: newSegmentValues,
436
+ segmentObj: segmentObjForValue,
413
437
  fieldNode: this.#fieldNode,
414
438
  timeRef: this.timeRef,
415
439
  }));
416
440
  }
417
- else {
418
- // this.setValue(undefined);
419
- // this.segmentValues = newSegmentValues;
420
- }
421
441
  }
422
442
  handleSegmentClick(e) {
423
443
  if (this.disabled.current) {
@@ -794,11 +814,12 @@ class TimeFieldHourSegmentState extends BaseTimeSegmentState {
794
814
  super(opts, root, "hour", SEGMENT_CONFIGS.hour);
795
815
  }
796
816
  onkeydown(e) {
817
+ const oldUpdateSegment = this.root.updateSegment.bind(this.root);
797
818
  if (isNumberString(e.key)) {
798
- const oldUpdateSegment = this.root.updateSegment.bind(this.root);
799
- // oxlint-disable-next-line no-explicit-any
819
+ this.root.hourInputDayPeriodHint =
820
+ this.root.hourCycle === 24 ? null : this.root.segmentValues.dayPeriod;
800
821
  this.root.updateSegment = (part, cb) => {
801
- const result = oldUpdateSegment(part, cb);
822
+ oldUpdateSegment(part, cb);
802
823
  // after updating hour, check if we need to display "12" instead of "0"
803
824
  if (part === "hour" && "hour" in this.root.segmentValues) {
804
825
  const hourValue = this.root.segmentValues.hour;
@@ -808,11 +829,11 @@ class TimeFieldHourSegmentState extends BaseTimeSegmentState {
808
829
  this.root.segmentValues.hour = "12";
809
830
  }
810
831
  }
811
- return result;
812
832
  };
813
833
  }
814
834
  super.onkeydown(e);
815
- this.root.updateSegment = this.root.updateSegment.bind(this.root);
835
+ this.root.updateSegment = oldUpdateSegment;
836
+ this.root.hourInputDayPeriodHint = null;
816
837
  }
817
838
  }
818
839
  class TimeFieldMinuteSegmentState extends BaseTimeSegmentState {
@@ -191,11 +191,11 @@ function isValidEvent(e, node) {
191
191
  if (!isElementOrSVGElement(target))
192
192
  return false;
193
193
  const targetIsContextMenuTrigger = Boolean(target.closest(`[${CONTEXT_MENU_TRIGGER_ATTR}]`));
194
+ const nodeIsContextMenu = Boolean(node.closest(`[${CONTEXT_MENU_CONTENT_ATTR}]`));
194
195
  if ("button" in e && e.button > 0 && !targetIsContextMenuTrigger)
195
196
  return false;
196
197
  if ("button" in e && e.button === 0 && targetIsContextMenuTrigger)
197
- return true;
198
- const nodeIsContextMenu = Boolean(node.closest(`[${CONTEXT_MENU_CONTENT_ATTR}]`));
198
+ return nodeIsContextMenu;
199
199
  if (targetIsContextMenuTrigger && nodeIsContextMenu)
200
200
  return false;
201
201
  const ownerDocument = getOwnerDocument(target);
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { mergeProps, srOnlyStylesString } from "svelte-toolbelt";
2
+ import { mergeProps, srOnlyStyles } from "svelte-toolbelt";
3
3
  import type { HTMLInputAttributes } from "svelte/elements";
4
4
 
5
5
  let { value = $bindable(), ...restProps }: HTMLInputAttributes = $props();
@@ -8,7 +8,12 @@
8
8
  mergeProps(restProps, {
9
9
  "aria-hidden": "true",
10
10
  tabindex: -1,
11
- style: srOnlyStylesString,
11
+ style: {
12
+ ...srOnlyStyles,
13
+ position: "absolute",
14
+ top: "0",
15
+ left: "0",
16
+ },
12
17
  })
13
18
  );
14
19
  </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bits-ui",
3
- "version": "2.17.1",
3
+ "version": "2.17.2",
4
4
  "license": "MIT",
5
5
  "repository": "github:huntabyte/bits-ui",
6
6
  "funding": "https://github.com/sponsors/huntabyte",