@allxsmith/bestax-bulma 5.48.1 → 5.49.1

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.
Files changed (39) hide show
  1. package/README.md +2 -3
  2. package/dist/bestax.css +1 -1
  3. package/dist/bestax.css.map +1 -1
  4. package/dist/esm/form/_pickerInternals/segmentMap.js +7 -7
  5. package/dist/esm/form/_pickerInternals/segmentMap.js.map +1 -1
  6. package/dist/esm/helpers/splitterLayout.js +166 -0
  7. package/dist/esm/helpers/splitterLayout.js.map +1 -0
  8. package/dist/esm/layout/Splitter.js +374 -0
  9. package/dist/esm/layout/Splitter.js.map +1 -0
  10. package/dist/extras.css +1 -1
  11. package/dist/extras.css.map +1 -1
  12. package/dist/index.cjs +533 -7
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.esm.js +1 -0
  15. package/dist/index.esm.js.map +1 -1
  16. package/dist/types/helpers/splitterLayout.d.ts +62 -0
  17. package/dist/types/index.d.ts +1 -0
  18. package/dist/types/layout/Splitter.d.ts +125 -0
  19. package/dist/types-cjs/helpers/splitterLayout.d.ts +62 -0
  20. package/dist/types-cjs/index.d.ts +1 -0
  21. package/dist/types-cjs/layout/Splitter.d.ts +125 -0
  22. package/dist/versions/bestax-no-dark-mode.css +1 -1
  23. package/dist/versions/bestax-no-dark-mode.css.map +1 -1
  24. package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
  25. package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
  26. package/dist/versions/bestax-no-helpers.css +1 -1
  27. package/dist/versions/bestax-no-helpers.css.map +1 -1
  28. package/dist/versions/bestax-prefixed.css +1 -1
  29. package/dist/versions/bestax-prefixed.css.map +1 -1
  30. package/package.json +2 -2
  31. package/src/scss/CLAUDE.md +1 -1
  32. package/src/scss/bestax.scss +1 -0
  33. package/src/scss/extras.scss +3 -0
  34. package/src/scss/layout/_index.scss +5 -0
  35. package/src/scss/layout/_splitter.scss +184 -0
  36. package/src/scss/versions/bestax-no-dark-mode.scss +1 -0
  37. package/src/scss/versions/bestax-no-helpers-prefixed.scss +1 -0
  38. package/src/scss/versions/bestax-no-helpers.scss +1 -0
  39. package/src/scss/versions/bestax-prefixed.scss +1 -0
package/dist/index.cjs CHANGED
@@ -18224,7 +18224,7 @@ function buildSegmentMap(format) {
18224
18224
  // Arithmetic helpers
18225
18225
  // ---------------------------------------------------------------------------
18226
18226
  const wrap$1 = (n, max) => ((n % max) + max) % max;
18227
- const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
18227
+ const clamp$1 = (n, min, max) => Math.max(min, Math.min(max, n));
18228
18228
  /**
18229
18229
  * Number of days in the month of `d` (handles leap Februarys). Built on
18230
18230
  * `makeDate`, so a year below 100 keeps its own leap rule.
@@ -18328,7 +18328,7 @@ function setSegmentValue(segment, currentDate, rawDigits, isPm) {
18328
18328
  // Hold: 00 is invalid; wait for the second digit (01..09).
18329
18329
  return { date: currentDate, advance: false };
18330
18330
  }
18331
- const clamped = clamp(value, 1, 12);
18331
+ const clamped = clamp$1(value, 1, 12);
18332
18332
  const day = next.getDate();
18333
18333
  next.setDate(1);
18334
18334
  next.setMonth(clamped - 1);
@@ -18343,7 +18343,7 @@ function setSegmentValue(segment, currentDate, rawDigits, isPm) {
18343
18343
  // Hold: 00 is invalid; wait for the second digit (01..09).
18344
18344
  return { date: currentDate, advance: false };
18345
18345
  }
18346
- const clamped = clamp(value, 1, daysInMonth(next));
18346
+ const clamped = clamp$1(value, 1, daysInMonth(next));
18347
18347
  next.setDate(clamped);
18348
18348
  // Advance when buffer is full, or first digit >=4 (no day 40+). First
18349
18349
  // digit 3 still waits for 30/31.
@@ -18356,7 +18356,7 @@ function setSegmentValue(segment, currentDate, rawDigits, isPm) {
18356
18356
  // Don't commit yet; wait for the second digit (01..09 are valid).
18357
18357
  return { date: currentDate, advance: false };
18358
18358
  }
18359
- const clamped = clamp(value, 1, 12);
18359
+ const clamped = clamp$1(value, 1, 12);
18360
18360
  const h24 = isPm ? (clamped % 12) + 12 : clamped % 12;
18361
18361
  next.setHours(h24);
18362
18362
  // Advance when the buffer is full, or when no valid 2-digit
@@ -18367,7 +18367,7 @@ function setSegmentValue(segment, currentDate, rawDigits, isPm) {
18367
18367
  return { date: next, advance };
18368
18368
  }
18369
18369
  // 24h: valid range 0..23.
18370
- const clamped = clamp(value, 0, 23);
18370
+ const clamped = clamp$1(value, 0, 23);
18371
18371
  next.setHours(clamped);
18372
18372
  // Advance when buffer is full, or when first digit is 3..9 (since
18373
18373
  // 30..99 are all out of range). First digits 0, 1, 2 can still accept
@@ -18376,14 +18376,14 @@ function setSegmentValue(segment, currentDate, rawDigits, isPm) {
18376
18376
  return { date: next, advance };
18377
18377
  }
18378
18378
  case 'minutes': {
18379
- const clamped = clamp(value, 0, 59);
18379
+ const clamped = clamp$1(value, 0, 59);
18380
18380
  next.setMinutes(clamped);
18381
18381
  // Advance when buffer full, or first digit 6..9 (can't form 60..99).
18382
18382
  const advance = rawDigits.length >= 2 || value >= 6;
18383
18383
  return { date: next, advance };
18384
18384
  }
18385
18385
  case 'seconds': {
18386
- const clamped = clamp(value, 0, 59);
18386
+ const clamped = clamp$1(value, 0, 59);
18387
18387
  next.setSeconds(clamped);
18388
18388
  const advance = rawDigits.length >= 2 || value >= 6;
18389
18389
  return { date: next, advance };
@@ -22693,6 +22693,530 @@ const Section = ({ size, className, children, color, bgColor, textColor, style,
22693
22693
  return (jsxRuntime.jsx("section", { className: sectionClasses, style: mergeBulmaStyles(bulmaHelperStyles, style), ...rest, children: children }));
22694
22694
  };
22695
22695
 
22696
+ // INTERNAL: deliberately not exported from src/index.ts.
22697
+ //
22698
+ // The arithmetic behind Splitter. Every size is a percentage of the length
22699
+ // the panes share, which is the splitter's length less its handles, and a
22700
+ // layout always adds up to 100.
22701
+ // Rounding leaves sizes a hair off, so anything this close counts as equal.
22702
+ const EPS = 1e-6;
22703
+ const clamp = (value, low, high) => Math.min(Math.max(value, low), high);
22704
+ /**
22705
+ * A limit in percent of `length` pixels. A number or a `%` is already one. A
22706
+ * `px` or `rem` length needs the length the panes share, and without it
22707
+ * there's no answer yet, so it's `null`. Anything else is `null` too.
22708
+ */
22709
+ function limitToPercent(limit, length, remPx) {
22710
+ if (limit === undefined)
22711
+ return null;
22712
+ if (typeof limit === 'number')
22713
+ return limit;
22714
+ const match = /^\s*(\d*\.?\d+)\s*(px|rem|%)?\s*$/.exec(limit);
22715
+ if (!match)
22716
+ return null;
22717
+ const value = parseFloat(match[1]);
22718
+ const unit = match[2] ?? '%';
22719
+ if (unit === '%')
22720
+ return value;
22721
+ if (!length)
22722
+ return null;
22723
+ return ((unit === 'rem' ? value * remPx : value) / length) * 100;
22724
+ }
22725
+ /**
22726
+ * The sizes panes start at: each its `defaultSize`, and the ones without
22727
+ * one sharing what's left equally. When they all have one, or those that do
22728
+ * take more than 100, the given ones are scaled to fit.
22729
+ */
22730
+ function defaultSizes(defaults) {
22731
+ const given = defaults.reduce((sum, d) => sum + (d ?? 0), 0);
22732
+ const free = defaults.filter(d => d === undefined).length;
22733
+ if (free === 0 || given > 100) {
22734
+ const scale = given > 0 ? 100 / given : 0;
22735
+ return defaults.map(d => d === undefined ? 0 : scale ? d * scale : 100 / defaults.length);
22736
+ }
22737
+ const share = (100 - given) / free;
22738
+ return defaults.map(d => (d === undefined ? share : d));
22739
+ }
22740
+ /** The range a pane can take in a layout: zero when it's shut. */
22741
+ const rangeOf = (bound, size) => bound.collapsible && size <= EPS
22742
+ ? { low: 0, high: 0 }
22743
+ : { low: bound.min, high: bound.max };
22744
+ /**
22745
+ * Moves each pane into its limits, taking what it needs from the nearest
22746
+ * panes with room to give, or giving what it has too much of to the nearest
22747
+ * with room to take. A shut pane stays shut. Limits that can't all hold are
22748
+ * met as far as they can be.
22749
+ */
22750
+ function fitSizes(sizes, bounds) {
22751
+ const next = [...sizes];
22752
+ const n = next.length;
22753
+ // Nearest first, so a change stays as local as it can.
22754
+ const byDistance = (i) => next
22755
+ .map((_, j) => j)
22756
+ .filter(j => j !== i)
22757
+ .sort((x, y) => Math.abs(x - i) - Math.abs(y - i) || x - y);
22758
+ for (let pass = 0; pass < n * 2; pass++) {
22759
+ let moved = false;
22760
+ for (let i = 0; i < n; i++) {
22761
+ const { low, high } = rangeOf(bounds[i], next[i]);
22762
+ if (next[i] < low - EPS) {
22763
+ let need = low - next[i];
22764
+ for (const j of byDistance(i)) {
22765
+ const give = Math.min(need, Math.max(0, next[j] - rangeOf(bounds[j], next[j]).low));
22766
+ next[j] -= give;
22767
+ next[i] += give;
22768
+ need -= give;
22769
+ if (give > EPS)
22770
+ moved = true;
22771
+ if (need <= EPS)
22772
+ break;
22773
+ }
22774
+ }
22775
+ else if (next[i] > high + EPS) {
22776
+ let spare = next[i] - high;
22777
+ for (const j of byDistance(i)) {
22778
+ const take = Math.min(spare, Math.max(0, rangeOf(bounds[j], next[j]).high - next[j]));
22779
+ next[j] += take;
22780
+ next[i] -= take;
22781
+ spare -= take;
22782
+ if (take > EPS)
22783
+ moved = true;
22784
+ if (spare <= EPS)
22785
+ break;
22786
+ }
22787
+ }
22788
+ }
22789
+ if (!moved)
22790
+ break;
22791
+ }
22792
+ return next;
22793
+ }
22794
+ /** Whether two layouts are the same, to within rounding. */
22795
+ const sameSizes = (a, b) => a.length === b.length && a.every((size, i) => Math.abs(size - b[i]) <= EPS);
22796
+ /**
22797
+ * The pane a handle speaks for: the one before it, unless only the one after
22798
+ * it can shut, so that Enter shuts the pane you'd expect.
22799
+ */
22800
+ const primaryOf = (handle, bounds) => !bounds[handle].collapsible && bounds[handle + 1].collapsible
22801
+ ? handle + 1
22802
+ : handle;
22803
+ /**
22804
+ * How far the pane before handle `a`'s line can go, between the two panes
22805
+ * beside it: `low` to `high` while both stay open, and `shut` and `full`
22806
+ * when one of them can close.
22807
+ */
22808
+ function pairRange(sizes, a, bounds) {
22809
+ const b = a + 1;
22810
+ const total = sizes[a] + sizes[b];
22811
+ const low = Math.max(bounds[a].min, total - bounds[b].max);
22812
+ const high = Math.min(bounds[a].max, total - bounds[b].min);
22813
+ return {
22814
+ total,
22815
+ low,
22816
+ high,
22817
+ // The pane before can shut if it may and the one after can take it all.
22818
+ shut: bounds[a].collapsible && total <= bounds[b].max + EPS,
22819
+ // The pane after can shut likewise.
22820
+ full: bounds[b].collapsible && total <= bounds[a].max + EPS,
22821
+ };
22822
+ }
22823
+ /**
22824
+ * The layout with the line of handle `a` moved so the pane before it is
22825
+ * `target`, within both panes' limits. A collapsible pane dragged below half
22826
+ * its minimum shuts, and one that's shut opens at its minimum once dragged
22827
+ * past that point.
22828
+ */
22829
+ function resizePair(sizes, a, target, bounds) {
22830
+ const b = a + 1;
22831
+ const { total, low, high, shut, full } = pairRange(sizes, a, bounds);
22832
+ let size;
22833
+ if (shut && target < bounds[a].min / 2)
22834
+ size = 0;
22835
+ else if (full && total - target < bounds[b].min / 2)
22836
+ size = total;
22837
+ else if (low > high + EPS)
22838
+ size = sizes[a];
22839
+ else
22840
+ size = clamp(target, low, high);
22841
+ const next = [...sizes];
22842
+ next[a] = size;
22843
+ next[b] = total - size;
22844
+ return next;
22845
+ }
22846
+ /**
22847
+ * The aria value range of a handle's primary pane: zero at the bottom when
22848
+ * it can shut, everything the pair holds at the top when the other pane can.
22849
+ */
22850
+ function primaryRange(sizes, a, primary, bounds) {
22851
+ const { total, low, high, shut, full } = pairRange(sizes, a, bounds);
22852
+ const minA = shut ? 0 : low;
22853
+ const maxA = full ? total : high;
22854
+ return primary === a
22855
+ ? { now: sizes[a], min: minA, max: maxA }
22856
+ : { now: sizes[a + 1], min: total - maxA, max: total - minA };
22857
+ }
22858
+
22859
+ const PaneContext = React.createContext(null);
22860
+ /**
22861
+ * One pane of a Splitter, sized by the splitter around it.
22862
+ *
22863
+ * @function
22864
+ * @param {SplitterPaneProps} props - Props for Splitter.Pane.
22865
+ * @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the pane's `<div>`.
22866
+ * @returns {JSX.Element} The rendered pane.
22867
+ */
22868
+ const SplitterPane = React.forwardRef(({ className, textColor, bgColor, label: _label, defaultSize: _defaultSize, minSize: _minSize, maxSize: _maxSize, collapsible: _collapsible, collapsed: _collapsed, defaultCollapsed: _defaultCollapsed, onCollapsedChange: _onCollapsedChange, style, children, ...props }, ref) => {
22869
+ const state = React.useContext(PaneContext);
22870
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
22871
+ color: textColor,
22872
+ backgroundColor: bgColor,
22873
+ ...props,
22874
+ });
22875
+ const paneClass = usePrefixedClassNames('splitter-pane', {
22876
+ 'is-collapsed': !!state?.collapsed,
22877
+ });
22878
+ return (jsxRuntime.jsx("div", { ref: ref, className: classNames(paneClass, bulmaHelperClasses, className), ...rest, id: state?.id ?? rest.id,
22879
+ // A percentage over a zero basis, so the server and the client lay
22880
+ // it out the same, with no measuring.
22881
+ style: state ? { ...style, flex: `${state.size} 1 0px` } : style, hidden: state?.collapsed || rest.hidden, children: children }));
22882
+ });
22883
+ SplitterPane.displayName = 'Splitter.Pane';
22884
+ const isPane = (child) => React.isValidElement(child) && child.type === SplitterPane;
22885
+ /** The bounds with pane `i`'s changed, for a move that mustn't snap it shut. */
22886
+ const withBound = (bounds, i, change) => bounds.map((bound, j) => (j === i ? { ...bound, ...change } : bound));
22887
+ /** The bounds with neither pane beside handle `h` able to shut. */
22888
+ const opened = (bounds, h) => withBound(withBound(bounds, h, { collapsible: false }), h + 1, {
22889
+ collapsible: false,
22890
+ });
22891
+ /**
22892
+ * The handle a pane's collapse goes through: the one that speaks for it. A
22893
+ * collapsible pane that isn't last always has the handle after it, so the
22894
+ * one left without is a last pane whose neighbour can collapse too, and it
22895
+ * goes through the handle before it.
22896
+ */
22897
+ const handleFor = (pane, bounds) => pane < bounds.length - 1 && primaryOf(pane, bounds) === pane
22898
+ ? pane
22899
+ : pane - 1;
22900
+ /**
22901
+ * The `Splitter` component lays out two or more panes side by side or one above the other, with a handle between each pair to drag or move by keyboard.
22902
+ *
22903
+ * Each handle is a focusable `separator`, named by the `label` of the pane
22904
+ * it speaks for. Sizes are percentages, so the server renders the layout
22905
+ * the browser does, and `minSize` and `maxSize` also take `px` and `rem`. A
22906
+ * `collapsible` pane shuts when dragged below half its minimum, and Enter on
22907
+ * its handle shuts it and brings it back. It doesn't store sizes itself:
22908
+ * keep them with `sizes` and `onSizesChange`.
22909
+ *
22910
+ * @function
22911
+ * @param {SplitterProps} props - Props for the Splitter component.
22912
+ * @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the root `<div>`.
22913
+ * @returns {JSX.Element} The rendered splitter.
22914
+ *
22915
+ * @example
22916
+ * <Splitter height={480}>
22917
+ * <Splitter.Pane label="Files" defaultSize={25} minSize="12rem">…</Splitter.Pane>
22918
+ * <Splitter.Pane label="Editor">…</Splitter.Pane>
22919
+ * </Splitter>
22920
+ */
22921
+ const SplitterRoot = React.forwardRef(({ className, style, textColor, bgColor, orientation = 'horizontal', height, sizes: sizesProp, onSizesChange, step = 1, children, ...props }, ref) => {
22922
+ const { bulmaHelperClasses, rest } = useBulmaClasses({
22923
+ color: textColor,
22924
+ backgroundColor: bgColor,
22925
+ ...props,
22926
+ });
22927
+ const vertical = orientation === 'vertical';
22928
+ const rootClass = usePrefixedClassNames('splitter', {
22929
+ 'is-vertical': vertical,
22930
+ });
22931
+ const handleClass = usePrefixedClassNames('splitter-handle');
22932
+ const activeClass = usePrefixedClassNames('is-active');
22933
+ const panes = flattenChildren(children).filter(isPane);
22934
+ const baseId = React.useId();
22935
+ const ids = panes.map((pane, i) => pane.props.id ?? `${baseId}-pane-${i}`);
22936
+ // The room the panes share, in pixels, and a rem, both measured in the
22937
+ // browser. Until then, only percentage limits apply.
22938
+ const [length, setLength] = React.useState(null);
22939
+ const [remPx, setRemPx] = React.useState(16);
22940
+ const bounds = panes.map(({ props: pane }) => ({
22941
+ min: Math.max(0, limitToPercent(pane.minSize, length, remPx) ?? 0),
22942
+ max: Math.min(100, limitToPercent(pane.maxSize, length, remPx) ?? 100),
22943
+ collapsible: !!pane.collapsible,
22944
+ }));
22945
+ const boundsKey = JSON.stringify(bounds);
22946
+ // Shuts pane `i`, giving its room to the pane across the handle that
22947
+ // speaks for it, when that pane can take it within its maximum, as a
22948
+ // drag would. A lone pane has no handle and nowhere to give its room.
22949
+ const collapseIn = (current, i) => {
22950
+ if (current.length < 2)
22951
+ return current;
22952
+ const h = handleFor(i, bounds);
22953
+ const across = h === i ? i + 1 : h;
22954
+ if (current[across] + current[i] > bounds[across].max + 1e-6) {
22955
+ return current;
22956
+ }
22957
+ const next = [...current];
22958
+ next[across] += next[i];
22959
+ next[i] = 0;
22960
+ return next;
22961
+ };
22962
+ const startSizes = () => {
22963
+ const base = defaultSizes(panes.map(pane => pane.props.defaultSize));
22964
+ let next = fitSizes(base, bounds);
22965
+ panes.forEach(({ props: pane }, i) => {
22966
+ if (pane.collapsible && (pane.collapsed ?? pane.defaultCollapsed)) {
22967
+ next = collapseIn(next, i);
22968
+ }
22969
+ });
22970
+ return next;
22971
+ };
22972
+ const paneKey = ids.join('|');
22973
+ const [state, setState] = React.useState(() => ({
22974
+ key: paneKey,
22975
+ sizes: startSizes(),
22976
+ }));
22977
+ // A different set of panes starts over from the defaults.
22978
+ let uncontrolled = state.sizes;
22979
+ if (state.key !== paneKey) {
22980
+ uncontrolled = startSizes();
22981
+ setState({ key: paneKey, sizes: uncontrolled });
22982
+ }
22983
+ const sizes = sizesProp && sizesProp.length === panes.length ? sizesProp : uncontrolled;
22984
+ // The size each pane comes back at after it's shut: the last size it had
22985
+ // open, or its default.
22986
+ const restoreRef = React.useRef([]);
22987
+ const defaults = defaultSizes(panes.map(pane => pane.props.defaultSize));
22988
+ sizes.forEach((size, i) => {
22989
+ var _a;
22990
+ if (size > 0)
22991
+ restoreRef.current[i] = size;
22992
+ else
22993
+ (_a = restoreRef.current)[i] ?? (_a[i] = defaults[i]);
22994
+ });
22995
+ const collapsedAt = (layout) => layout.map((size, i) => bounds[i].collapsible && size <= 1e-6);
22996
+ // Moves the layout on, telling the app and each pane whose collapse
22997
+ // changed, unless the change came from its own `collapsed`. A move that
22998
+ // would collapse or open a pane whose `collapsed` the app holds only
22999
+ // asks: the app's answer comes back through `collapsed`.
23000
+ const sizesRef = React.useRef(sizes);
23001
+ sizesRef.current = sizes;
23002
+ const panesRef = React.useRef(panes);
23003
+ panesRef.current = panes;
23004
+ const commit = (next, fromProp = false) => {
23005
+ const previous = sizesRef.current;
23006
+ if (sameSizes(previous, next))
23007
+ return;
23008
+ const before = collapsedAt(previous);
23009
+ const after = collapsedAt(next);
23010
+ const flipped = after.flatMap((shut, i) => shut !== before[i] ? [i] : []);
23011
+ if (!fromProp &&
23012
+ flipped.some(i => panesRef.current[i].props.collapsed !== undefined)) {
23013
+ flipped.forEach(i => panesRef.current[i].props.onCollapsedChange?.(after[i]));
23014
+ return;
23015
+ }
23016
+ sizesRef.current = next;
23017
+ if (!sizesProp)
23018
+ setState(current => ({ ...current, sizes: next }));
23019
+ onSizesChange?.(next);
23020
+ if (!fromProp) {
23021
+ flipped.forEach(i => panesRef.current[i].props.onCollapsedChange?.(after[i]));
23022
+ }
23023
+ };
23024
+ // Brings pane `i` back at its restore size, taken from across the handle
23025
+ // that speaks for it.
23026
+ // A lone pane never shuts, so there's always a handle here.
23027
+ const expand = (current, i) => {
23028
+ const h = handleFor(i, bounds);
23029
+ const target = restoreRef.current[i];
23030
+ const total = current[h] + current[h + 1];
23031
+ return resizePair(current, h, h === i ? target : total - target,
23032
+ // Opening one pane snaps neither shut: it stops at the limits.
23033
+ opened(bounds, h));
23034
+ };
23035
+ // A pane's own `collapsed` moving: shut it or bring it back, before the
23036
+ // browser paints.
23037
+ const collapsedProps = panes.map(pane => pane.props.collapsible ? pane.props.collapsed : undefined);
23038
+ const collapsedKey = JSON.stringify(collapsedProps);
23039
+ useClientLayoutEffect(() => {
23040
+ const current = sizesRef.current;
23041
+ const shut = collapsedAt(current);
23042
+ let next = current;
23043
+ collapsedProps.forEach((wanted, i) => {
23044
+ if (wanted === undefined || wanted === shut[i])
23045
+ return;
23046
+ next = wanted ? collapseIn(next, i) : expand(next, i);
23047
+ });
23048
+ commit(next, true);
23049
+ // collapsedKey stands for collapsedProps.
23050
+ // eslint-disable-next-line react-hooks/exhaustive-deps
23051
+ }, [collapsedKey]);
23052
+ // Measuring: the room the panes share, so `px` and `rem` limits can turn
23053
+ // into percentages, then the layout fitted to them.
23054
+ const rootRef = React.useRef(null);
23055
+ const setRefs = React.useCallback((node) => {
23056
+ rootRef.current = node;
23057
+ if (typeof ref === 'function')
23058
+ return ref(node);
23059
+ if (ref)
23060
+ ref.current = node;
23061
+ }, [ref]);
23062
+ // The room the panes share: the root's length less its handles'.
23063
+ const roomOf = React.useCallback(() => {
23064
+ const root = rootRef.current;
23065
+ /* istanbul ignore next: measuring only runs while it's mounted */
23066
+ if (!root)
23067
+ return null;
23068
+ const along = (el) => {
23069
+ const rect = el.getBoundingClientRect();
23070
+ return vertical ? rect.height : rect.width;
23071
+ };
23072
+ const handles = Array.from(root.children).filter(child => child.matches('[role="separator"]'));
23073
+ const room = along(root) - handles.reduce((sum, handle) => sum + along(handle), 0);
23074
+ return room > 0 ? room : null;
23075
+ }, [vertical]);
23076
+ const measure = React.useCallback(() => {
23077
+ setLength(roomOf());
23078
+ setRemPx(parseFloat(getComputedStyle(document.documentElement).fontSize) || 16);
23079
+ }, [roomOf]);
23080
+ useClientLayoutEffect(() => {
23081
+ measure();
23082
+ const root = rootRef.current;
23083
+ if (!root || typeof ResizeObserver === 'undefined')
23084
+ return;
23085
+ const resizes = new ResizeObserver(measure);
23086
+ resizes.observe(root);
23087
+ return () => resizes.disconnect();
23088
+ }, [measure]);
23089
+ // Limits that changed, from measuring or from props: fit the layout.
23090
+ useClientLayoutEffect(() => {
23091
+ commit(fitSizes(sizesRef.current, bounds));
23092
+ // boundsKey stands for bounds.
23093
+ // eslint-disable-next-line react-hooks/exhaustive-deps
23094
+ }, [boundsKey]);
23095
+ const rtl = () => !vertical &&
23096
+ !!rootRef.current &&
23097
+ getComputedStyle(rootRef.current).direction === 'rtl';
23098
+ // ---- Dragging ----
23099
+ const [dragging, setDragging] = React.useState(null);
23100
+ const dragRef = React.useRef(null);
23101
+ const onPointerDown = (h) => (event) => {
23102
+ if (event.button !== 0)
23103
+ return;
23104
+ const pixels = roomOf();
23105
+ if (!pixels)
23106
+ return;
23107
+ event.currentTarget.setPointerCapture?.(event.pointerId);
23108
+ dragRef.current = {
23109
+ handle: h,
23110
+ start: vertical ? event.clientY : event.clientX,
23111
+ startSizes: sizesRef.current,
23112
+ pixels,
23113
+ flip: rtl(),
23114
+ };
23115
+ setDragging(h);
23116
+ };
23117
+ const onPointerMove = (event) => {
23118
+ const drag = dragRef.current;
23119
+ if (!drag)
23120
+ return;
23121
+ const moved = (vertical ? event.clientY : event.clientX) - drag.start;
23122
+ const delta = ((drag.flip ? -moved : moved) / drag.pixels) * 100;
23123
+ commit(resizePair(sizesRef.current, drag.handle, drag.startSizes[drag.handle] + delta, bounds));
23124
+ };
23125
+ const endDrag = (event) => {
23126
+ if (!dragRef.current)
23127
+ return;
23128
+ dragRef.current = null;
23129
+ event.currentTarget.releasePointerCapture?.(event.pointerId);
23130
+ setDragging(null);
23131
+ };
23132
+ // While dragging, the resize cursor shows wherever the pointer goes.
23133
+ React.useEffect(() => {
23134
+ if (dragging === null)
23135
+ return;
23136
+ const body = document.body.style;
23137
+ const { cursor, userSelect } = body;
23138
+ body.cursor = vertical ? 'row-resize' : 'col-resize';
23139
+ body.userSelect = 'none';
23140
+ return () => {
23141
+ body.cursor = cursor;
23142
+ body.userSelect = userSelect;
23143
+ };
23144
+ }, [dragging, vertical]);
23145
+ // Double-click: the two panes back to their default share of the room.
23146
+ const onDoubleClick = (h) => () => {
23147
+ const current = sizesRef.current;
23148
+ const total = current[h] + current[h + 1];
23149
+ const pair = defaults[h] + defaults[h + 1];
23150
+ const target = pair > 0 ? (defaults[h] / pair) * total : total / 2;
23151
+ const next = [...current];
23152
+ next[h] = target;
23153
+ next[h + 1] = total - target;
23154
+ commit(fitSizes(next, bounds));
23155
+ };
23156
+ // ---- Keys ----
23157
+ const onKeyDown = (h) => (event) => {
23158
+ const current = sizesRef.current;
23159
+ const primary = primaryOf(h, bounds);
23160
+ const by = event.shiftKey ? step * 10 : step;
23161
+ const back = vertical ? 'ArrowUp' : 'ArrowLeft';
23162
+ const forward = vertical ? 'ArrowDown' : 'ArrowRight';
23163
+ // The line moves the way the key points, so right to left, moving
23164
+ // right shrinks the pane before, which sits on the right.
23165
+ const sign = rtl() ? -1 : 1;
23166
+ // Keys move within the limits; Enter is what shuts a pane.
23167
+ const open = opened(bounds, h);
23168
+ let next = null;
23169
+ if (event.key === back || event.key === forward) {
23170
+ const direction = (event.key === forward ? 1 : -1) * sign;
23171
+ next = resizePair(current, h, current[h] + direction * by, open);
23172
+ }
23173
+ else if (event.key === 'Home' || event.key === 'End') {
23174
+ const range = primaryRange(current, h, primary, open);
23175
+ const size = event.key === 'Home' ? range.min : range.max;
23176
+ const total = current[h] + current[h + 1];
23177
+ next = resizePair(current, h, primary === h ? size : total - size, open);
23178
+ }
23179
+ else if (event.key === 'Enter' && bounds[primary].collapsible) {
23180
+ next =
23181
+ current[primary] > 1e-6
23182
+ ? collapseIn(current, primary)
23183
+ : expand(current, primary);
23184
+ }
23185
+ if (!next)
23186
+ return;
23187
+ event.preventDefault();
23188
+ commit(next);
23189
+ };
23190
+ // A handle names its pane, so a pane without a label leaves it nameless.
23191
+ const unnamed = panes
23192
+ .slice(0, -1)
23193
+ .some((_, h) => !panes[primaryOf(h, bounds)].props.label);
23194
+ React.useEffect(() => {
23195
+ if (unnamed) {
23196
+ warnOnce('Splitter:unlabelled', '[bestax-bulma] <Splitter> has a handle whose pane has no label. ' +
23197
+ 'Give each Splitter.Pane a label, so a screen reader can say ' +
23198
+ 'which pane a handle resizes.');
23199
+ }
23200
+ }, [unnamed]);
23201
+ const collapsed = collapsedAt(sizes);
23202
+ const items = [];
23203
+ panes.forEach((pane, i) => {
23204
+ items.push(jsxRuntime.jsx(PaneContext.Provider
23205
+ // Children.toArray, under flattenChildren, keys every child.
23206
+ , { value: { id: ids[i], size: sizes[i], collapsed: collapsed[i] }, children: pane }, `pane-${String(pane.key)}`));
23207
+ if (i === panes.length - 1)
23208
+ return;
23209
+ const primary = primaryOf(i, bounds);
23210
+ const range = primaryRange(sizes, i, primary, bounds);
23211
+ items.push(jsxRuntime.jsx("div", { className: classNames(handleClass, dragging === i && activeClass), role: "separator", tabIndex: 0, "aria-orientation": vertical ? 'horizontal' : 'vertical', "aria-valuenow": Math.round(range.now), "aria-valuemin": Math.round(range.min), "aria-valuemax": Math.round(range.max), "aria-controls": ids[primary], "aria-label": panes[primary].props.label, onPointerDown: onPointerDown(i), onPointerMove: onPointerMove, onPointerUp: endDrag, onPointerCancel: endDrag, onDoubleClick: onDoubleClick(i), onKeyDown: onKeyDown(i) }, `handle-${i}`));
23212
+ });
23213
+ return (jsxRuntime.jsx("div", { ref: setRefs, className: classNames(rootClass, bulmaHelperClasses, className), style: height !== undefined ? { ...style, height } : style, ...rest, children: items }));
23214
+ });
23215
+ SplitterRoot.displayName = 'Splitter';
23216
+ const Splitter = withSubComponents(SplitterRoot, {
23217
+ Pane: SplitterPane,
23218
+ });
23219
+
22696
23220
  exports.Autocomplete = Autocomplete;
22697
23221
  exports.Avatar = Avatar;
22698
23222
  exports.Avatars = Avatars;
@@ -22837,6 +23361,8 @@ exports.Sidebar = Sidebar;
22837
23361
  exports.Skeleton = Skeleton;
22838
23362
  exports.Slider = Slider;
22839
23363
  exports.Span = Span;
23364
+ exports.Splitter = Splitter;
23365
+ exports.SplitterPane = SplitterPane;
22840
23366
  exports.Step = Step;
22841
23367
  exports.Steps = Steps;
22842
23368
  exports.Strong = Strong;