craft-native 0.0.492 → 0.0.497

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.
@@ -1313,13 +1313,7 @@ internal class CraftNativeChrome(
1313
1313
  action.layoutParams = LayoutParams(dp(48), dp(48)).apply { marginStart = if (grouped) 0 else dp(8) }
1314
1314
  }
1315
1315
  accessory.layoutParams = LayoutParams(dp(if (grouped) 48 else 64), dp(if (grouped) 48 else 64)).apply { marginStart = if (grouped) 0 else dp(8) }
1316
- val divider = GradientDrawable().apply { setColor(Color.argb(48, 128, 128, 128)); setSize(dp(1), dp(1)) }
1317
- for (row in listOf(accessoryGroup, companionRow)) {
1318
- row.showDividers = if (grouped) SHOW_DIVIDER_MIDDLE else SHOW_DIVIDER_NONE
1319
- row.dividerDrawable = if (grouped) divider.constantState?.newDrawable() else null
1320
- row.dividerPadding = dp(12)
1321
- }
1322
- // An empty companion row must not leave a divider above a single icon.
1316
+ // A group is one control: no divider between its actions, as on iOS.
1323
1317
  companionRow.visibility = if (companionViews.isEmpty()) View.GONE else View.VISIBLE
1324
1318
  }
1325
1319
 
package/dist/cli.js CHANGED
@@ -3246,7 +3246,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
3246
3246
  `);
3247
3247
  }
3248
3248
  // package.json
3249
- var version = "0.0.492";
3249
+ var version = "0.0.497";
3250
3250
 
3251
3251
  // bin/cli.ts
3252
3252
  var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
package/dist/index.cjs CHANGED
@@ -376,7 +376,7 @@ function craftBinaryNotFoundMessage(triedPath) {
376
376
  `);
377
377
  }
378
378
  // package.json
379
- var version = "0.0.492";
379
+ var version = "0.0.497";
380
380
 
381
381
  // src/sdk-version.ts
382
382
  var SDK_VERSION = version;
package/dist/index.js CHANGED
@@ -117,7 +117,7 @@ function craftBinaryNotFoundMessage(triedPath) {
117
117
  `);
118
118
  }
119
119
  // package.json
120
- var version = "0.0.492";
120
+ var version = "0.0.497";
121
121
 
122
122
  // src/sdk-version.ts
123
123
  var SDK_VERSION = version;
@@ -1248,7 +1248,9 @@ final class CraftHybridController: UITabBarController, UITabBarControllerDelegat
1248
1248
  private var companionButtons: [UIButton] = []
1249
1249
  private var companionModels: [CraftChrome.Tab] = []
1250
1250
  private var companionGlasses: [UIVisualEffectView] = []
1251
- private var accessorySeparators: [UIView] = []
1251
+ /// Whether the shared surface was last configured for a group; its
1252
+ /// material is replaced only when that changes, never on a badge update.
1253
+ private var accessorySurfaceGrouped: Bool?
1252
1254
  private let accessoryBadge = UILabel()
1253
1255
  private let accessoryGlass = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial))
1254
1256
  private static let tabPictures = NSCache<NSURL, UIImage>()
@@ -1506,7 +1508,6 @@ final class CraftHybridController: UITabBarController, UITabBarControllerDelegat
1506
1508
  glass.clipsToBounds = true
1507
1509
  glass.isUserInteractionEnabled = false
1508
1510
  view.addSubview(glass)
1509
- view.addSubview(button)
1510
1511
  companionGlasses.append(glass)
1511
1512
  companionButtons.append(button)
1512
1513
  }
@@ -1515,22 +1516,22 @@ final class CraftHybridController: UITabBarController, UITabBarControllerDelegat
1515
1516
  companionButtons[index].accessibilityTraits = chrome.accessorySelected(action) ? [.button, .selected] : [.button]
1516
1517
  }
1517
1518
  let grouped = model?.accessoryLayout != nil
1519
+ // A group is one control, as the map controls in Maps are: its
1520
+ // actions live inside the shared surface, so the glass lights the
1521
+ // one being pressed and keeps every symbol legible over the page.
1522
+ // Separate actions stay in the shell, each on its own surface.
1523
+ let actionHost: UIView = grouped ? accessoryGlass.contentView : view
1518
1524
  for glass in companionGlasses { glass.isHidden = grouped }
1519
- for button in companionButtons { button.tintColor = grouped ? .label : nil }
1525
+ for button in companionButtons {
1526
+ if button.superview !== actionHost { actionHost.addSubview(button) }
1527
+ button.tintColor = grouped ? .label : nil
1528
+ button.configuration?.preferredSymbolConfigurationForImage = Self.accessorySymbol(grouped: grouped)
1529
+ }
1520
1530
  accessoryButton.tintColor = grouped ? .label : nil
1521
- for separator in accessorySeparators { separator.removeFromSuperview() }
1522
- accessorySeparators = grouped ? companionButtons.map { _ in
1523
- let separator = UIView()
1524
- separator.backgroundColor = .separator
1525
- separator.isUserInteractionEnabled = false
1526
- separator.isAccessibilityElement = false
1527
- view.addSubview(separator)
1528
- return separator
1529
- } : []
1530
1531
  if let model {
1531
1532
  var configuration = UIButton.Configuration.plain()
1532
1533
  configuration.image = UIImage(systemName: model.symbol)
1533
- configuration.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(pointSize: 22, weight: .regular)
1534
+ configuration.preferredSymbolConfigurationForImage = Self.accessorySymbol(grouped: grouped)
1534
1535
  configuration.title = model.iconOnly ? nil : model.displayTitle
1535
1536
  configuration.subtitle = model.iconOnly ? nil : model.badge
1536
1537
  configuration.imagePadding = model.iconOnly ? 0 : 8
@@ -1559,22 +1560,17 @@ final class CraftHybridController: UITabBarController, UITabBarControllerDelegat
1559
1560
  // Older systems have no bottom-accessory container. Keep the action
1560
1561
  // available above the system bar, without recreating the tab control.
1561
1562
  #if compiler(>=6.2)
1562
- if #available(iOS 26.0, *) {
1563
- bottomAccessory = nil
1564
- accessoryGlass.effect = UIGlassEffect()
1565
- }
1563
+ if #available(iOS 26.0, *) { bottomAccessory = nil }
1566
1564
  #endif
1567
- if model != nil, accessoryButton.superview !== view {
1565
+ if model != nil, accessoryGlass.superview !== view || accessoryButton.superview !== actionHost {
1568
1566
  accessoryButton.removeFromSuperview()
1569
- view.addSubview(accessoryGlass)
1570
- view.addSubview(accessoryButton)
1567
+ if accessoryGlass.superview !== view { view.addSubview(accessoryGlass) }
1568
+ actionHost.addSubview(accessoryButton)
1571
1569
  view.addSubview(accessoryBadge)
1572
1570
  }
1573
1571
  accessoryButton.isHidden = model == nil
1574
1572
  accessoryGlass.isHidden = model == nil
1575
- accessoryGlass.isUserInteractionEnabled = false
1576
- accessoryGlass.layer.cornerRadius = grouped ? 26 : 24
1577
- accessoryGlass.clipsToBounds = true
1573
+ configureAccessorySurface(grouped: grouped)
1578
1574
  accessoryButton.backgroundColor = .clear
1579
1575
  accessoryButton.layer.cornerRadius = 24
1580
1576
  accessoryBadge.text = model?.badge
@@ -1591,15 +1587,63 @@ final class CraftHybridController: UITabBarController, UITabBarControllerDelegat
1591
1587
  // companion-only update creates new controls or a shelf changes mode.
1592
1588
  if accessoryGlass.superview === view {
1593
1589
  view.bringSubviewToFront(accessoryGlass)
1594
- for button in companionButtons { view.bringSubviewToFront(button) }
1595
- view.bringSubviewToFront(accessoryButton)
1596
- for separator in accessorySeparators { view.bringSubviewToFront(separator) }
1590
+ if !grouped {
1591
+ for button in companionButtons { view.bringSubviewToFront(button) }
1592
+ view.bringSubviewToFront(accessoryButton)
1593
+ }
1597
1594
  view.bringSubviewToFront(accessoryBadge)
1598
1595
  }
1599
1596
  for nav in stacks.values { nav.additionalSafeAreaInsets.bottom = safeAreaForTabBar() }
1600
1597
  view.setNeedsLayout()
1601
1598
  }
1602
1599
 
1600
+ /// Symbols in a group carry the weight of the system's own map controls;
1601
+ /// a separate action keeps the lighter bar-button weight.
1602
+ private static func accessorySymbol(grouped: Bool) -> UIImage.SymbolConfiguration {
1603
+ UIImage.SymbolConfiguration(pointSize: grouped ? 20 : 22, weight: grouped ? .medium : .regular)
1604
+ }
1605
+
1606
+ /// The surface under the floating actions. A group is a capsule of
1607
+ /// interactive Liquid Glass on iOS 26, shaped by the system so its rim,
1608
+ /// lensing and shadow are not clipped away; before iOS 26 it is the tab
1609
+ /// bar's material with a hairline. A single action stays a plain circle.
1610
+ private func configureAccessorySurface(grouped: Bool) {
1611
+ accessoryGlass.isUserInteractionEnabled = grouped
1612
+ if accessorySurfaceGrouped != grouped {
1613
+ accessorySurfaceGrouped = grouped
1614
+ var system = false
1615
+ #if compiler(>=6.2)
1616
+ if #available(iOS 26.0, *) {
1617
+ let glass = UIGlassEffect()
1618
+ glass.isInteractive = grouped
1619
+ accessoryGlass.effect = glass
1620
+ accessoryGlass.cornerConfiguration = grouped ? .capsule() : .corners(radius: 24)
1621
+ system = true
1622
+ }
1623
+ #endif
1624
+ if system {
1625
+ accessoryGlass.layer.cornerRadius = 0
1626
+ accessoryGlass.layer.borderWidth = 0
1627
+ accessoryGlass.clipsToBounds = false
1628
+ } else {
1629
+ accessoryGlass.effect = UIBlurEffect(style: grouped ? .systemUltraThinMaterial : .systemMaterial)
1630
+ accessoryGlass.layer.cornerCurve = .continuous
1631
+ accessoryGlass.layer.borderWidth = grouped ? 0.5 : 0
1632
+ accessoryGlass.layer.borderColor = UIColor.white.withAlphaComponent(0.35).cgColor
1633
+ accessoryGlass.clipsToBounds = true
1634
+ }
1635
+ }
1636
+ // Before iOS 26 the capsule follows the group's shorter side.
1637
+ if accessoryGlass.clipsToBounds {
1638
+ accessoryGlass.layer.cornerRadius = grouped ? Self.accessoryGroupBreadth / 2 : 24
1639
+ }
1640
+ }
1641
+
1642
+ /// A group's breadth, and each action's length along it: the system map
1643
+ /// controls' proportions, every target at least 48 points.
1644
+ static let accessoryGroupBreadth: CGFloat = 48
1645
+ static let accessoryGroupStep: CGFloat = 52
1646
+
1603
1647
  @objc private func openTabAccessory() {
1604
1648
  if let accessoryModel { hybrid.open(accessoryModel.id) }
1605
1649
  }
@@ -1617,6 +1661,11 @@ final class CraftHybridController: UITabBarController, UITabBarControllerDelegat
1617
1661
  }
1618
1662
  #endif
1619
1663
  let iconOnly = accessoryModel?.iconOnly == true
1664
+ if let axis = accessoryModel?.accessoryLayout {
1665
+ layoutAccessoryGroup(vertical: axis == "vertical")
1666
+ CraftChrome.shared.layoutChanged(tabBar.isHidden ? 0 : max(0, view.bounds.maxY - tabBar.frame.minY))
1667
+ return
1668
+ }
1620
1669
  let height: CGFloat = iconOnly ? 48 : 44
1621
1670
  let width: CGFloat = iconOnly ? 48 : max(0, view.bounds.width - 32)
1622
1671
  let trailingX = view.effectiveUserInterfaceLayoutDirection == .rightToLeft
@@ -1624,25 +1673,12 @@ final class CraftHybridController: UITabBarController, UITabBarControllerDelegat
1624
1673
  let x = iconOnly ? trailingX : 16
1625
1674
  accessoryButton.frame = CGRect(x: x, y: tabBar.frame.minY - height - (iconOnly ? 8 : 0), width: width, height: height)
1626
1675
  accessoryGlass.frame = accessoryButton.frame
1627
- let layout = accessoryModel?.accessoryLayout
1628
1676
  for (index, button) in companionButtons.enumerated() {
1629
1677
  let offset = CGFloat(companionButtons.count - index)
1630
- if layout == "vertical" {
1631
- button.frame = accessoryButton.frame.offsetBy(dx: 0, dy: -offset * height)
1632
- } else {
1633
- let direction: CGFloat = view.effectiveUserInterfaceLayoutDirection == .rightToLeft ? 1 : -1
1634
- button.frame = accessoryButton.frame.offsetBy(dx: direction * offset * (width + (layout == nil ? 12 : 0)), dy: 0)
1635
- }
1678
+ let direction: CGFloat = view.effectiveUserInterfaceLayoutDirection == .rightToLeft ? 1 : -1
1679
+ button.frame = accessoryButton.frame.offsetBy(dx: direction * offset * (width + 12), dy: 0)
1636
1680
  companionGlasses[index].frame = button.frame
1637
- if layout != nil {
1638
- accessoryGlass.frame = accessoryGlass.frame.union(button.frame)
1639
- let line = 1 / (view.window?.screen.scale ?? 2)
1640
- accessorySeparators[index].frame = layout == "vertical"
1641
- ? CGRect(x: button.frame.minX + 12, y: button.frame.maxY - line / 2, width: width - 24, height: line)
1642
- : CGRect(x: view.effectiveUserInterfaceLayoutDirection == .rightToLeft ? button.frame.minX : button.frame.maxX - line / 2, y: button.frame.minY + 12, width: line, height: height - 24)
1643
- }
1644
1681
  }
1645
- if layout != nil { accessoryGlass.frame = accessoryGlass.frame.insetBy(dx: -2, dy: -2) }
1646
1682
  let badgeWidth = max(18, accessoryBadge.intrinsicContentSize.width + 8)
1647
1683
  accessoryBadge.frame = CGRect(x: accessoryButton.frame.maxX - badgeWidth + 4, y: accessoryButton.frame.minY - 3, width: badgeWidth, height: 18)
1648
1684
  // A compact action floats over the page, not across its whole width.
@@ -1651,6 +1687,31 @@ final class CraftHybridController: UITabBarController, UITabBarControllerDelegat
1651
1687
  CraftChrome.shared.layoutChanged(tabBar.isHidden ? 0 : max(0, view.bounds.maxY - tabBar.frame.minY) + accessoryInset)
1652
1688
  }
1653
1689
 
1690
+ /// One capsule above the trailing end of the tab bar, companions first
1691
+ /// and the primary action last. Nothing divides the actions: the glass
1692
+ /// is one control, and each action is found by its place and its symbol.
1693
+ private func layoutAccessoryGroup(vertical: Bool) {
1694
+ let breadth = Self.accessoryGroupBreadth
1695
+ let step = Self.accessoryGroupStep
1696
+ let buttons = companionButtons + [accessoryButton]
1697
+ let length = step * CGFloat(buttons.count)
1698
+ let size = vertical ? CGSize(width: breadth, height: length) : CGSize(width: length, height: breadth)
1699
+ let rightToLeft = view.effectiveUserInterfaceLayoutDirection == .rightToLeft
1700
+ // The tab bar's own platter sits this far in from the screen's edge.
1701
+ let margin: CGFloat = 22
1702
+ let x = rightToLeft ? view.safeAreaInsets.left + margin : view.bounds.maxX - view.safeAreaInsets.right - size.width - margin
1703
+ accessoryGlass.frame = CGRect(x: x, y: tabBar.frame.minY - size.height - 10, width: size.width, height: size.height)
1704
+ for (index, button) in buttons.enumerated() {
1705
+ let place = CGFloat(rightToLeft && !vertical ? buttons.count - 1 - index : index) * step
1706
+ button.frame = vertical
1707
+ ? CGRect(x: 0, y: place, width: breadth, height: step)
1708
+ : CGRect(x: place, y: 0, width: step, height: breadth)
1709
+ }
1710
+ let badgeWidth = max(18, accessoryBadge.intrinsicContentSize.width + 8)
1711
+ let primary = accessoryButton.convert(accessoryButton.bounds, to: view)
1712
+ accessoryBadge.frame = CGRect(x: primary.maxX - badgeWidth + 4, y: primary.minY - 3, width: badgeWidth, height: 18)
1713
+ }
1714
+
1654
1715
  private var webAttached = false
1655
1716
 
1656
1717
  private func installTabSwipeGestures() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "craft-native",
3
- "version": "0.0.492",
3
+ "version": "0.0.497",
4
4
  "type": "module",
5
5
  "description": "Build desktop apps with web languages - TypeScript SDK for Craft",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",