@window-splitter/state 0.3.1 → 0.4.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/esm/index.js CHANGED
@@ -138,6 +138,7 @@ export function initializePanel(item) {
138
138
  id: item.id,
139
139
  collapseAnimation: item.collapseAnimation,
140
140
  default: item.default ? parseUnit(item.default) : undefined,
141
+ isStaticAtRest: item.isStaticAtRest,
141
142
  };
142
143
  return { ...data, currentValue: makePixelUnit(-1) };
143
144
  }
@@ -159,7 +160,7 @@ export function parseUnit(unit) {
159
160
  return makePixelUnit(parseFloat(unit));
160
161
  }
161
162
  if (unit.endsWith("%")) {
162
- return makePercentUnit(parseFloat(unit));
163
+ return makePercentUnit(parseFloat(unit) / 100);
163
164
  }
164
165
  throw new Error(`Invalid unit: ${unit}`);
165
166
  }
@@ -180,7 +181,7 @@ function getUnitPixelValue(context, unit) {
180
181
  const parsed = unit === "1fr" ? parseUnit(unit) : unit;
181
182
  return parsed.type === "pixel"
182
183
  ? parsed.value
183
- : new Big(parsed.value).div(100).mul(getGroupSize(context));
184
+ : parsed.value.mul(getGroupSize(context));
184
185
  }
185
186
  /** Clamp a new `currentValue` given the panel's constraints. */
186
187
  function clampUnit(context, item, value) {
@@ -280,8 +281,7 @@ function panelHasSpace(context, item, adjustment) {
280
281
  return (item.currentValue.value.gte(getUnitPixelValue(context, item.min)) &&
281
282
  item.currentValue.value.lt(getUnitPixelValue(context, item.max)));
282
283
  }
283
- return (item.currentValue.value.gt(getUnitPixelValue(context, item.min)) &&
284
- item.currentValue.value.lte(getUnitPixelValue(context, item.max)));
284
+ return item.currentValue.value.gt(getUnitPixelValue(context, item.min));
285
285
  }
286
286
  /** Search in a `direction` for a panel that still has space to expand. */
287
287
  function findPanelWithSpace(context, items, start, direction, adjustment, disregardCollapseBuffer) {
@@ -305,9 +305,10 @@ function getStaticWidth(context) {
305
305
  if (isPanelHandle(item)) {
306
306
  width = width.add(item.size.value);
307
307
  }
308
- else if (isPanelData(item) &&
309
- item.collapsed &&
310
- item.currentValue.type === "pixel") {
308
+ else if (item.collapsed && item.currentValue.type === "pixel") {
309
+ width = width.add(item.currentValue.value);
310
+ }
311
+ else if (item.isStaticAtRest) {
311
312
  width = width.add(item.currentValue.value);
312
313
  }
313
314
  }
@@ -317,7 +318,7 @@ function formatUnit(unit) {
317
318
  if (unit.type === "pixel") {
318
319
  return `${unit.value.toNumber()}px`;
319
320
  }
320
- return `${unit.value.toNumber()}%`;
321
+ return `${unit.value.mul(100).toNumber()}%`;
321
322
  }
322
323
  export function getPanelGroupPixelSizes(context) {
323
324
  return prepareItems(context).map((i) => isPanelData(i)
@@ -354,6 +355,10 @@ export function buildTemplate(context) {
354
355
  const min = formatUnit(item.min);
355
356
  if (item.currentValue.type === "pixel" &&
356
357
  item.currentValue.value.toNumber() !== -1) {
358
+ if (item.isStaticAtRest) {
359
+ const max = item.max === "1fr" ? "100%" : formatUnit(item.max);
360
+ return `clamp(${min}, ${formatUnit(item.currentValue)}, ${max})`;
361
+ }
357
362
  return formatUnit(item.currentValue);
358
363
  }
359
364
  else if (item.currentValue.type === "percent") {
@@ -364,10 +369,24 @@ export function buildTemplate(context) {
364
369
  return formatUnit(item.collapsedSize);
365
370
  }
366
371
  else if (item.default) {
367
- return formatUnit(item.default);
372
+ const siblingHasFill = context.items.some((i) => isPanelData(i) &&
373
+ i.id !== item.id &&
374
+ !i.collapsed &&
375
+ (i.max === "1fr" ||
376
+ (i.max.type === "percent" && i.max.value.eq(100))));
377
+ // If a sibling has a fill, this item doesn't need to expand
378
+ // So we can just use the default value
379
+ if (siblingHasFill) {
380
+ return formatUnit(item.default);
381
+ }
382
+ // Use 1fr so that panel fills ths space if needed
383
+ const max = item.max === "1fr" ? "1fr" : formatUnit(item.max);
384
+ return `minmax(${formatUnit(item.default)}, ${max})`;
385
+ }
386
+ else {
387
+ const max = item.max === "1fr" ? "1fr" : formatUnit(item.max);
388
+ return `minmax(${min}, ${max})`;
368
389
  }
369
- const max = item.max === "1fr" ? "1fr" : formatUnit(item.max);
370
- return `minmax(${min}, ${max})`;
371
390
  }
372
391
  return formatUnit(item.size);
373
392
  })
@@ -445,11 +464,11 @@ export function prepareItems(context) {
445
464
  const newItems = [];
446
465
  for (const item of context.items) {
447
466
  if (!item || !isPanelData(item)) {
448
- newItems.push(item);
467
+ newItems.push({ ...item });
449
468
  continue;
450
469
  }
451
470
  if (item.currentValue.type === "pixel") {
452
- newItems.push(item);
471
+ newItems.push({ ...item });
453
472
  continue;
454
473
  }
455
474
  const pixel = new Big(getGroupSize(context))
@@ -503,6 +522,10 @@ function updateLayout(context, dragEvent) {
503
522
  const potentialNewValue = panelAfter.currentValue.value.add(new Big(newDragOvershoot).mul(isInRightBuffer ? moveDirection : 1));
504
523
  const min = getUnitPixelValue(context, panelAfter.min);
505
524
  const isInDragBugger = newDragOvershoot.abs().lt(COLLAPSE_THRESHOLD) &&
525
+ // Let the panel expand at it's min size
526
+ !panelAfter.currentValue.value
527
+ .add(newDragOvershoot.abs())
528
+ .gte(panelAfter.min.value) &&
506
529
  panelAfter.collapsible &&
507
530
  panelAfter.collapsed &&
508
531
  (isInLeftOvershoot || isInRightOvershoot);
@@ -515,9 +538,9 @@ function updateLayout(context, dragEvent) {
515
538
  }
516
539
  }
517
540
  // Don't let the panel collapse until the threshold is reached
518
- if (panelBefore.collapsible &&
519
- panelBefore.currentValue.value ===
520
- getUnitPixelValue(context, panelBefore.min)) {
541
+ if (!dragEvent.disregardCollapseBuffer &&
542
+ panelBefore.collapsible &&
543
+ panelBefore.currentValue.value.eq(getUnitPixelValue(context, panelBefore.min))) {
521
544
  const potentialNewValue = panelBefore.currentValue.value.sub(newDragOvershoot.abs());
522
545
  if (newDragOvershoot.abs().lt(COLLAPSE_THRESHOLD) &&
523
546
  potentialNewValue.gt(getUnitPixelValue(context, panelBefore.collapsedSize))) {
@@ -564,7 +587,6 @@ function updateLayout(context, dragEvent) {
564
587
  .sub(panelAfterNewValue
565
588
  // Then re-add the move amount
566
589
  .add(Math.abs(moveAmount)));
567
- panelAfter.collapsed = false;
568
590
  if (extra.gt(0)) {
569
591
  panelAfterNewValue = panelAfterNewValue.add(extra);
570
592
  }
@@ -574,14 +596,18 @@ function updateLayout(context, dragEvent) {
574
596
  .minus(panelAfterPreviousValue)
575
597
  // And then re-apply the movement value
576
598
  .minus(Math.abs(moveAmount)));
599
+ if (panelBeforeNewValue.lt(panelBefore.min.value)) {
600
+ // TODO this should probably distribute the space between the panels?
601
+ return { dragOvershoot: newDragOvershoot };
602
+ }
603
+ panelAfter.collapsed = false;
577
604
  if (panelAfter.onCollapseChange?.current &&
578
605
  !panelAfter.collapseIsControlled &&
579
606
  !dragEvent.controlled) {
580
607
  panelAfter.onCollapseChange.current(false);
581
608
  }
582
609
  }
583
- const panelBeforeIsAboutToCollapse = panelBefore.currentValue.value ===
584
- getUnitPixelValue(context, panelBefore.min);
610
+ const panelBeforeIsAboutToCollapse = panelBefore.currentValue.value.eq(getUnitPixelValue(context, panelBefore.min));
585
611
  // If the panel was expanded and now is at it's min size, collapse it
586
612
  if (!dragEvent.disregardCollapseBuffer &&
587
613
  panelBefore.collapsible &&
@@ -607,8 +633,7 @@ function updateLayout(context, dragEvent) {
607
633
  panelAfter.currentValue = { type: "pixel", value: panelAfterNewValue };
608
634
  const leftoverSpace = new Big(getGroupSize(context)).minus(newItems.reduce((acc, b) => acc.add(isPanelData(b) ? b.currentValue.value : b.size.value), new Big(0)));
609
635
  if (!leftoverSpace.eq(0)) {
610
- panelBefore.currentValue.value =
611
- panelBefore.currentValue.value.add(leftoverSpace);
636
+ panelBefore.currentValue.value = clampUnit(context, panelBefore, panelBefore.currentValue.value.add(leftoverSpace));
612
637
  }
613
638
  return { items: newItems };
614
639
  }
@@ -629,7 +654,7 @@ function commitLayout(context) {
629
654
  });
630
655
  const staticWidth = getStaticWidth({ ...context, items: newItems });
631
656
  newItems.forEach((item, index) => {
632
- if (item.type !== "panel" || item.collapsed) {
657
+ if (item.type !== "panel" || item.collapsed || item.isStaticAtRest) {
633
658
  return;
634
659
  }
635
660
  newItems[index] = {
@@ -699,19 +724,74 @@ function applyDeltaInBothDirections(context, newItems, itemIndex, delta) {
699
724
  direction = direction === 1 ? -1 : 1;
700
725
  }
701
726
  }
727
+ /**
728
+ * A layout might overflow at small screen sizes.
729
+ * This function tries to fix that by:
730
+ *
731
+ * 1. It will try to collapse a panel if it can.
732
+ */
733
+ function handleOverflow(context) {
734
+ // If we haven't measured yet we can't do anything
735
+ if (context.items.some((i) => isPanelData(i) && i.currentValue.value.eq(-1))) {
736
+ return context;
737
+ }
738
+ const groupSize = new Big(getGroupSize(context));
739
+ const nonStaticWidth = groupSize.sub(getStaticWidth(context).toNumber());
740
+ const pixelItems = context.items.map((i) => {
741
+ if (isPanelHandle(i))
742
+ return i.size.value;
743
+ if (i.collapsed)
744
+ return getUnitPixelValue(context, i.currentValue);
745
+ const pixel = (i.currentValue.type === "pixel" && i.currentValue.value) ||
746
+ i.currentValue.value.mul(nonStaticWidth);
747
+ return clampUnit(context, i, pixel);
748
+ });
749
+ const totalSize = pixelItems.reduce((acc, i) => acc.add(i), new Big(0));
750
+ const overflow = totalSize.abs().sub(groupSize);
751
+ if (overflow.eq(0) || groupSize.eq(0)) {
752
+ return context;
753
+ }
754
+ let newContext = { ...context, items: prepareItems(context) };
755
+ const collapsiblePanel = newContext.items.find((i) => Boolean(isPanelData(i) && i.collapsible));
756
+ if (collapsiblePanel) {
757
+ const collapsiblePanelIndex = newContext.items.findIndex((i) => i.id === collapsiblePanel.id);
758
+ const handleId = getHandleForPanelId(newContext, collapsiblePanel.id);
759
+ const sizeChange = collapsiblePanel.currentValue.value.sub(getUnitPixelValue(newContext, collapsiblePanel.collapsedSize));
760
+ // Try to collapse the panel
761
+ newContext = {
762
+ ...newContext,
763
+ ...iterativelyUpdateLayout({
764
+ handleId: handleId.item.id,
765
+ delta: sizeChange,
766
+ direction: (handleId.direction * -1),
767
+ context: {
768
+ ...newContext,
769
+ // act like its the old size so the space is distributed correctly
770
+ size: { width: totalSize.toNumber(), height: totalSize.toNumber() },
771
+ },
772
+ }),
773
+ };
774
+ // Then remove all the overflow
775
+ applyDeltaInBothDirections(newContext, newContext.items, collapsiblePanelIndex, overflow.neg());
776
+ }
777
+ return { ...newContext, items: commitLayout(newContext) };
778
+ }
779
+ function getDeltaForEvent(context, event) {
780
+ const panel = getPanelWithId(context, event.panelId);
781
+ if (event.type === "expandPanel") {
782
+ return new Big(panel.sizeBeforeCollapse ?? getUnitPixelValue(context, panel.min)).minus(panel.currentValue.value);
783
+ }
784
+ const collapsedSize = getUnitPixelValue(context, panel.collapsedSize);
785
+ return panel.currentValue.value.minus(collapsedSize);
786
+ }
702
787
  const animationActor = fromPromise(({ input: { send, context, event } }) => new Promise((resolve) => {
703
788
  const panel = getPanelWithId(context, event.panelId);
704
789
  const handle = getHandleForPanelId(context, event.panelId);
705
790
  let direction = new Big(handle.direction);
706
- let fullDelta = new Big(0);
707
- if (event.type === "expandPanel") {
708
- fullDelta = new Big(panel.sizeBeforeCollapse ?? getUnitPixelValue(context, panel.min)).minus(panel.currentValue.value);
709
- }
710
- else {
711
- const collapsedSize = getUnitPixelValue(context, panel.collapsedSize);
791
+ const fullDelta = getDeltaForEvent(context, event);
792
+ if (event.type === "collapsePanel") {
712
793
  panel.sizeBeforeCollapse = panel.currentValue.value.toNumber();
713
794
  direction = direction.mul(new Big(-1));
714
- fullDelta = panel.currentValue.value.minus(collapsedSize);
715
795
  }
716
796
  const fps = 60;
717
797
  const { duration, ease } = getCollapseAnimation(panel);
@@ -759,7 +839,7 @@ export const groupMachine = createMachine({
759
839
  states: {
760
840
  idle: {
761
841
  on: {
762
- setActualItemsSize: { actions: ["recordActualItemSize"] },
842
+ setActualItemsSize: { actions: ["recordActualItemSize", "onResize"] },
763
843
  dragHandleStart: { target: "dragging" },
764
844
  setPanelPixelSize: {
765
845
  actions: [
@@ -778,6 +858,8 @@ export const groupMachine = createMachine({
778
858
  { target: "togglingCollapse" },
779
859
  ],
780
860
  expandPanel: [
861
+ // This will match if we can't expand and the expansion won't happen
862
+ { guard: "cannotExpandPanel" },
781
863
  {
782
864
  actions: "notifyCollapseToggle",
783
865
  guard: "shouldNotifyCollapseToggle",
@@ -850,6 +932,29 @@ export const groupMachine = createMachine({
850
932
  const panel = getPanelWithId(context, event.panelId);
851
933
  return panel.collapseIsControlled === true;
852
934
  },
935
+ cannotExpandPanel: ({ context, event }) => {
936
+ isEvent(event, ["expandPanel"]);
937
+ const delta = getDeltaForEvent(context, event);
938
+ const handle = getHandleForPanelId(context, event.panelId);
939
+ const pixelItems = prepareItems(context);
940
+ let interimContext = { ...context, items: pixelItems };
941
+ interimContext = {
942
+ ...interimContext,
943
+ ...iterativelyUpdateLayout({
944
+ context: interimContext,
945
+ handleId: handle.item.id,
946
+ controlled: event.controlled,
947
+ delta: delta,
948
+ direction: handle.direction,
949
+ }),
950
+ };
951
+ const updatedPanel = interimContext.items.find((i) => i.id === event.panelId);
952
+ const totalSize = interimContext.items.reduce((acc, i) => acc.add(isPanelData(i) ? i.currentValue.value : i.size.value), new Big(0));
953
+ const didExpand = updatedPanel &&
954
+ isPanelData(updatedPanel) &&
955
+ !updatedPanel.currentValue.value.eq(updatedPanel.collapsedSize.value);
956
+ return totalSize.gt(getGroupSize(context)) || !didExpand;
957
+ },
853
958
  },
854
959
  actors: {
855
960
  animation: animationActor,
@@ -889,11 +994,14 @@ export const groupMachine = createMachine({
889
994
  return context.items;
890
995
  },
891
996
  }),
892
- updateSize: assign({
893
- size: ({ event }) => {
894
- isEvent(event, ["setSize"]);
895
- return event.size;
896
- },
997
+ updateSize: enqueueActions(({ context, event, enqueue }) => {
998
+ isEvent(event, ["setSize"]);
999
+ if (event.handleOverflow) {
1000
+ enqueue.assign(handleOverflow({ ...context, size: event.size }));
1001
+ }
1002
+ else {
1003
+ enqueue.assign({ size: event.size });
1004
+ }
897
1005
  }),
898
1006
  recordActualItemSize: assign({
899
1007
  items: ({ context, event }) => {
@@ -1034,9 +1142,13 @@ export const groupMachine = createMachine({
1034
1142
  onResize: ({ context }) => {
1035
1143
  for (const item of context.items) {
1036
1144
  if (isPanelData(item)) {
1145
+ const pixel = clampUnit(context, item, getUnitPixelValue(context, item.currentValue));
1146
+ const groupSize = getGroupSize(context);
1037
1147
  item.onResize?.current?.({
1038
- pixel: getUnitPixelValue(context, item.currentValue).toNumber(),
1039
- percentage: getUnitPercentageValue(getGroupSize(context), item.currentValue),
1148
+ pixel: pixel.toNumber(),
1149
+ percentage: groupSize > 0
1150
+ ? pixel.div(getGroupSize(context)).toNumber()
1151
+ : -1,
1040
1152
  });
1041
1153
  }
1042
1154
  }