@window-splitter/state 0.3.2 → 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
  }
@@ -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
  }
@@ -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") {
@@ -459,11 +464,11 @@ export function prepareItems(context) {
459
464
  const newItems = [];
460
465
  for (const item of context.items) {
461
466
  if (!item || !isPanelData(item)) {
462
- newItems.push(item);
467
+ newItems.push({ ...item });
463
468
  continue;
464
469
  }
465
470
  if (item.currentValue.type === "pixel") {
466
- newItems.push(item);
471
+ newItems.push({ ...item });
467
472
  continue;
468
473
  }
469
474
  const pixel = new Big(getGroupSize(context))
@@ -517,6 +522,10 @@ function updateLayout(context, dragEvent) {
517
522
  const potentialNewValue = panelAfter.currentValue.value.add(new Big(newDragOvershoot).mul(isInRightBuffer ? moveDirection : 1));
518
523
  const min = getUnitPixelValue(context, panelAfter.min);
519
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) &&
520
529
  panelAfter.collapsible &&
521
530
  panelAfter.collapsed &&
522
531
  (isInLeftOvershoot || isInRightOvershoot);
@@ -529,9 +538,9 @@ function updateLayout(context, dragEvent) {
529
538
  }
530
539
  }
531
540
  // Don't let the panel collapse until the threshold is reached
532
- if (panelBefore.collapsible &&
533
- panelBefore.currentValue.value ===
534
- getUnitPixelValue(context, panelBefore.min)) {
541
+ if (!dragEvent.disregardCollapseBuffer &&
542
+ panelBefore.collapsible &&
543
+ panelBefore.currentValue.value.eq(getUnitPixelValue(context, panelBefore.min))) {
535
544
  const potentialNewValue = panelBefore.currentValue.value.sub(newDragOvershoot.abs());
536
545
  if (newDragOvershoot.abs().lt(COLLAPSE_THRESHOLD) &&
537
546
  potentialNewValue.gt(getUnitPixelValue(context, panelBefore.collapsedSize))) {
@@ -578,7 +587,6 @@ function updateLayout(context, dragEvent) {
578
587
  .sub(panelAfterNewValue
579
588
  // Then re-add the move amount
580
589
  .add(Math.abs(moveAmount)));
581
- panelAfter.collapsed = false;
582
590
  if (extra.gt(0)) {
583
591
  panelAfterNewValue = panelAfterNewValue.add(extra);
584
592
  }
@@ -588,14 +596,18 @@ function updateLayout(context, dragEvent) {
588
596
  .minus(panelAfterPreviousValue)
589
597
  // And then re-apply the movement value
590
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;
591
604
  if (panelAfter.onCollapseChange?.current &&
592
605
  !panelAfter.collapseIsControlled &&
593
606
  !dragEvent.controlled) {
594
607
  panelAfter.onCollapseChange.current(false);
595
608
  }
596
609
  }
597
- const panelBeforeIsAboutToCollapse = panelBefore.currentValue.value ===
598
- getUnitPixelValue(context, panelBefore.min);
610
+ const panelBeforeIsAboutToCollapse = panelBefore.currentValue.value.eq(getUnitPixelValue(context, panelBefore.min));
599
611
  // If the panel was expanded and now is at it's min size, collapse it
600
612
  if (!dragEvent.disregardCollapseBuffer &&
601
613
  panelBefore.collapsible &&
@@ -621,8 +633,7 @@ function updateLayout(context, dragEvent) {
621
633
  panelAfter.currentValue = { type: "pixel", value: panelAfterNewValue };
622
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)));
623
635
  if (!leftoverSpace.eq(0)) {
624
- panelBefore.currentValue.value =
625
- panelBefore.currentValue.value.add(leftoverSpace);
636
+ panelBefore.currentValue.value = clampUnit(context, panelBefore, panelBefore.currentValue.value.add(leftoverSpace));
626
637
  }
627
638
  return { items: newItems };
628
639
  }
@@ -643,7 +654,7 @@ function commitLayout(context) {
643
654
  });
644
655
  const staticWidth = getStaticWidth({ ...context, items: newItems });
645
656
  newItems.forEach((item, index) => {
646
- if (item.type !== "panel" || item.collapsed) {
657
+ if (item.type !== "panel" || item.collapsed || item.isStaticAtRest) {
647
658
  return;
648
659
  }
649
660
  newItems[index] = {
@@ -713,19 +724,74 @@ function applyDeltaInBothDirections(context, newItems, itemIndex, delta) {
713
724
  direction = direction === 1 ? -1 : 1;
714
725
  }
715
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
+ }
716
787
  const animationActor = fromPromise(({ input: { send, context, event } }) => new Promise((resolve) => {
717
788
  const panel = getPanelWithId(context, event.panelId);
718
789
  const handle = getHandleForPanelId(context, event.panelId);
719
790
  let direction = new Big(handle.direction);
720
- let fullDelta = new Big(0);
721
- if (event.type === "expandPanel") {
722
- fullDelta = new Big(panel.sizeBeforeCollapse ?? getUnitPixelValue(context, panel.min)).minus(panel.currentValue.value);
723
- }
724
- else {
725
- const collapsedSize = getUnitPixelValue(context, panel.collapsedSize);
791
+ const fullDelta = getDeltaForEvent(context, event);
792
+ if (event.type === "collapsePanel") {
726
793
  panel.sizeBeforeCollapse = panel.currentValue.value.toNumber();
727
794
  direction = direction.mul(new Big(-1));
728
- fullDelta = panel.currentValue.value.minus(collapsedSize);
729
795
  }
730
796
  const fps = 60;
731
797
  const { duration, ease } = getCollapseAnimation(panel);
@@ -792,6 +858,8 @@ export const groupMachine = createMachine({
792
858
  { target: "togglingCollapse" },
793
859
  ],
794
860
  expandPanel: [
861
+ // This will match if we can't expand and the expansion won't happen
862
+ { guard: "cannotExpandPanel" },
795
863
  {
796
864
  actions: "notifyCollapseToggle",
797
865
  guard: "shouldNotifyCollapseToggle",
@@ -864,6 +932,29 @@ export const groupMachine = createMachine({
864
932
  const panel = getPanelWithId(context, event.panelId);
865
933
  return panel.collapseIsControlled === true;
866
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
+ },
867
958
  },
868
959
  actors: {
869
960
  animation: animationActor,
@@ -903,11 +994,14 @@ export const groupMachine = createMachine({
903
994
  return context.items;
904
995
  },
905
996
  }),
906
- updateSize: assign({
907
- size: ({ event }) => {
908
- isEvent(event, ["setSize"]);
909
- return event.size;
910
- },
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
+ }
911
1005
  }),
912
1006
  recordActualItemSize: assign({
913
1007
  items: ({ context, event }) => {
@@ -1049,9 +1143,12 @@ export const groupMachine = createMachine({
1049
1143
  for (const item of context.items) {
1050
1144
  if (isPanelData(item)) {
1051
1145
  const pixel = clampUnit(context, item, getUnitPixelValue(context, item.currentValue));
1146
+ const groupSize = getGroupSize(context);
1052
1147
  item.onResize?.current?.({
1053
1148
  pixel: pixel.toNumber(),
1054
- percentage: pixel.div(getGroupSize(context)).toNumber(),
1149
+ percentage: groupSize > 0
1150
+ ? pixel.div(getGroupSize(context)).toNumber()
1151
+ : -1,
1055
1152
  });
1056
1153
  }
1057
1154
  }