@octane-xplat/macos-renderer 0.11.1 → 0.12.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/package.json +1 -1
- package/src/index.ts +109 -26
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -381,7 +381,7 @@ class GridLayoutView extends NSView {
|
|
|
381
381
|
}
|
|
382
382
|
|
|
383
383
|
// Absolute's host — children position via their own left/top/right/bottom
|
|
384
|
-
// layout-child props; the layout() pass
|
|
384
|
+
// layout-child props; the layout() pass updates parent-anchored constraints.
|
|
385
385
|
class AbsoluteLayoutView extends NSView {
|
|
386
386
|
static {
|
|
387
387
|
NativeClass(this)
|
|
@@ -637,30 +637,24 @@ function syncMarginHost(node: ElementNode) {
|
|
|
637
637
|
}
|
|
638
638
|
|
|
639
639
|
const insets = marginInsetsOf(node)
|
|
640
|
-
const gravity = stackGravity(parent, node)
|
|
641
|
-
const index = gravityInsertIndex(parent, node, gravity)
|
|
642
640
|
if (insets && !node.marginHost) {
|
|
641
|
+
const gravity = stackGravity(parent, node)
|
|
642
|
+
const index = gravityInsertIndex(parent, node, gravity)
|
|
643
643
|
stack.removeArrangedSubview(node.view)
|
|
644
644
|
node.view.removeFromSuperview()
|
|
645
645
|
const host = makeMarginHost(node)
|
|
646
646
|
stack.insertViewAtIndexInGravity(host, index, gravity)
|
|
647
647
|
queueLayoutReconcile(node.container, parent)
|
|
648
648
|
setLayoutAction(node, node.props?.onLayoutChanged)
|
|
649
|
-
} else if (
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
node.marginHost = null
|
|
654
|
-
node.marginConstraints = null
|
|
655
|
-
stack.insertViewAtIndexInGravity(node.view, index, gravity)
|
|
656
|
-
queueLayoutReconcile(node.container, parent)
|
|
657
|
-
setLayoutAction(node, node.props?.onLayoutChanged)
|
|
658
|
-
} else if (insets && node.marginHost) {
|
|
649
|
+
} else if (node.marginHost) {
|
|
650
|
+
// Once wrapped, the host stays arranged for the node's lifetime —
|
|
651
|
+
// unwrapping would detach a first-responder ancestor and end live
|
|
652
|
+
// editing. Zeroed constants collapse it to the child's frame.
|
|
659
653
|
const [leading, trailing, top, bottom] = node.marginConstraints
|
|
660
|
-
leading.constant = insets
|
|
661
|
-
trailing.constant = -insets
|
|
662
|
-
top.constant = insets
|
|
663
|
-
bottom.constant = -insets
|
|
654
|
+
leading.constant = insets?.left ?? 0
|
|
655
|
+
trailing.constant = 0 - (insets?.right ?? 0)
|
|
656
|
+
top.constant = insets?.top ?? 0
|
|
657
|
+
bottom.constant = 0 - (insets?.bottom ?? 0)
|
|
664
658
|
}
|
|
665
659
|
}
|
|
666
660
|
|
|
@@ -744,21 +738,86 @@ function gravityInsertIndex(parent: ElementNode, node: ElementNode, gravity: num
|
|
|
744
738
|
return index
|
|
745
739
|
}
|
|
746
740
|
|
|
741
|
+
/** Snapshot one gravity area's arranged views. The bridge returns an
|
|
742
|
+
* NSArray; `count` may be a method or a property depending on interop. */
|
|
743
|
+
function stackViewsInGravity(stack: any, gravity: number) {
|
|
744
|
+
const views = typeof stack?.viewsInGravity === 'function' ? stack.viewsInGravity(gravity) : null
|
|
745
|
+
const count = typeof views?.count === 'function' ? views.count() : (views?.count ?? 0)
|
|
746
|
+
const arrangedViews: any[] = []
|
|
747
|
+
for (let index = 0; index < count; index++) {
|
|
748
|
+
arrangedViews.push(views.objectAtIndex(index))
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return arrangedViews
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/** Reconcile one gravity area with the children's desired views. Only
|
|
755
|
+
* re-parented children land in `moved` — detaching a view that is (or
|
|
756
|
+
* contains) the first responder ends the active editing session. */
|
|
757
|
+
function syncStackGravityArea(
|
|
758
|
+
parent: ElementNode,
|
|
759
|
+
stack: any,
|
|
760
|
+
gravity: number,
|
|
761
|
+
moved: Set<ElementNode>,
|
|
762
|
+
) {
|
|
763
|
+
const desired: ElementNode[] = []
|
|
764
|
+
for (const child of parent.children) {
|
|
765
|
+
if (child.view && stackGravity(parent, child) === gravity) {
|
|
766
|
+
desired.push(child)
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const keep = new Set(desired.map((child) => arrangedView(child)))
|
|
771
|
+
const arrangedViews = stackViewsInGravity(stack, gravity)
|
|
772
|
+
for (let index = arrangedViews.length - 1; index >= 0; index--) {
|
|
773
|
+
if (!keep.has(arrangedViews[index])) {
|
|
774
|
+
stack.removeArrangedSubview(arrangedViews[index])
|
|
775
|
+
arrangedViews[index].removeFromSuperview()
|
|
776
|
+
arrangedViews.splice(index, 1)
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
for (let index = 0; index < desired.length; index++) {
|
|
781
|
+
const child = desired[index]
|
|
782
|
+
const view = arrangedView(child)
|
|
783
|
+
if (arrangedViews[index] === view) {
|
|
784
|
+
continue
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const from = arrangedViews.indexOf(view)
|
|
788
|
+
if (from >= 0) {
|
|
789
|
+
stack.removeArrangedSubview(view)
|
|
790
|
+
view.removeFromSuperview()
|
|
791
|
+
arrangedViews.splice(from, 1)
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
stack.insertViewAtIndexInGravity(view, index, gravity)
|
|
795
|
+
arrangedViews.splice(index, 0, view)
|
|
796
|
+
moved.add(child)
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
747
800
|
function moveStackChildren(parent: ElementNode) {
|
|
748
801
|
const stack = parent.childHost ?? parent.view
|
|
749
802
|
if (typeof stack?.addViewInGravity !== 'function') {
|
|
750
803
|
return
|
|
751
804
|
}
|
|
752
805
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
806
|
+
// Diff each gravity area against the live arrangement so unchanged
|
|
807
|
+
// children are never re-parented — AppKit's endEditingFor: fires when a
|
|
808
|
+
// first-responder ancestor leaves its superview. Orientation flips keep
|
|
809
|
+
// identical gravity values (Leading == Top, Trailing == Bottom), so a
|
|
810
|
+
// pure orientation change is a no-op here.
|
|
811
|
+
const moved = new Set<ElementNode>()
|
|
812
|
+
for (const gravity of [
|
|
813
|
+
NSStackViewGravity.Leading,
|
|
814
|
+
NSStackViewGravity.Center,
|
|
815
|
+
NSStackViewGravity.Trailing,
|
|
816
|
+
]) {
|
|
817
|
+
syncStackGravityArea(parent, stack, gravity, moved)
|
|
818
|
+
}
|
|
757
819
|
|
|
758
|
-
|
|
759
|
-
stack.removeArrangedSubview(arranged)
|
|
760
|
-
arranged.removeFromSuperview()
|
|
761
|
-
stack.addViewInGravity(arranged, stackGravity(parent, child))
|
|
820
|
+
for (const child of moved) {
|
|
762
821
|
// Removing a view drops constraints to its old superview, including
|
|
763
822
|
// percentage dimensions. Reinstall them after reattaching.
|
|
764
823
|
applySizeConstraints(child)
|
|
@@ -1402,10 +1461,34 @@ function pinLayoutChild(
|
|
|
1402
1461
|
}
|
|
1403
1462
|
}
|
|
1404
1463
|
|
|
1464
|
+
const same = (a: number, b: number) => a === b || (Number.isNaN(a) && Number.isNaN(b))
|
|
1465
|
+
if (
|
|
1466
|
+
same(Number(pins.leading.constant), x) &&
|
|
1467
|
+
same(Number(pins.top.constant), top) &&
|
|
1468
|
+
same(Number(pins.width.constant), width) &&
|
|
1469
|
+
same(Number(pins.height.constant), height)
|
|
1470
|
+
) {
|
|
1471
|
+
return
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1405
1474
|
pins.leading.constant = x
|
|
1406
1475
|
pins.top.constant = top
|
|
1407
1476
|
pins.width.constant = width
|
|
1408
1477
|
pins.height.constant = height
|
|
1478
|
+
// Constant writes inside a layout() pass apply only on the next solve, and
|
|
1479
|
+
// a needsLayout mark made now is cleared when that pass ends. Defer the
|
|
1480
|
+
// mark so the next layoutSubtreeIfNeeded/display cycle applies the pins —
|
|
1481
|
+
// without it, real-window resizes leave children at stale frames. The bare
|
|
1482
|
+
// vm slices in tests have no microtask queue, so fall back to a direct
|
|
1483
|
+
// mark there.
|
|
1484
|
+
const mark = () => {
|
|
1485
|
+
parentView.needsLayout = true
|
|
1486
|
+
}
|
|
1487
|
+
if (typeof queueMicrotask === 'function') {
|
|
1488
|
+
queueMicrotask(mark)
|
|
1489
|
+
} else {
|
|
1490
|
+
mark()
|
|
1491
|
+
}
|
|
1409
1492
|
}
|
|
1410
1493
|
|
|
1411
1494
|
/** Drop the pins a grid/absolute parent installed — a constraint that keeps
|
|
@@ -3378,7 +3461,7 @@ function insert(
|
|
|
3378
3461
|
deactivateSizeConstraints(node)
|
|
3379
3462
|
queueLayoutReconcile(container, parent)
|
|
3380
3463
|
} else {
|
|
3381
|
-
if (marginInsetsOf(node)) {
|
|
3464
|
+
if (marginInsetsOf(node) && !node.marginHost) {
|
|
3382
3465
|
makeMarginHost(node)
|
|
3383
3466
|
setLayoutAction(node, node.props?.onLayoutChanged)
|
|
3384
3467
|
}
|