@allxsmith/bestax-bulma 5.48.0 → 5.49.0
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/dist/bestax.css +1 -1
- package/dist/bestax.css.map +1 -1
- package/dist/esm/form/_pickerInternals/segmentMap.js +7 -7
- package/dist/esm/form/_pickerInternals/segmentMap.js.map +1 -1
- package/dist/esm/helpers/splitterLayout.js +166 -0
- package/dist/esm/helpers/splitterLayout.js.map +1 -0
- package/dist/esm/layout/Splitter.js +374 -0
- package/dist/esm/layout/Splitter.js.map +1 -0
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +533 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +1 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/types/helpers/splitterLayout.d.ts +62 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/layout/Splitter.d.ts +125 -0
- package/dist/types-cjs/helpers/splitterLayout.d.ts +62 -0
- package/dist/types-cjs/index.d.ts +1 -0
- package/dist/types-cjs/layout/Splitter.d.ts +125 -0
- package/dist/versions/bestax-no-dark-mode.css +1 -1
- package/dist/versions/bestax-no-dark-mode.css.map +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
- package/dist/versions/bestax-no-helpers.css +1 -1
- package/dist/versions/bestax-no-helpers.css.map +1 -1
- package/dist/versions/bestax-prefixed.css +1 -1
- package/dist/versions/bestax-prefixed.css.map +1 -1
- package/package.json +3 -2
- package/src/scss/CLAUDE.md +1 -1
- package/src/scss/bestax.scss +1 -0
- package/src/scss/extras.scss +3 -0
- package/src/scss/layout/_index.scss +5 -0
- package/src/scss/layout/_splitter.scss +184 -0
- package/src/scss/versions/bestax-no-dark-mode.scss +1 -0
- package/src/scss/versions/bestax-no-helpers-prefixed.scss +1 -0
- package/src/scss/versions/bestax-no-helpers.scss +1 -0
- 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;
|