craft-native 0.0.408 → 0.0.410
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/cli.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/macos/src/engine.d.ts +12 -0
- package/dist/macos/src/index.d.ts +1 -0
- package/dist/macos/src/index.js +33 -0
- package/dist/macos/templates/CraftMacApplication.swift +57 -1
- package/dist/macos/templates/CraftMacRenderer.swift +46 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3047,7 +3047,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
|
|
|
3047
3047
|
`);
|
|
3048
3048
|
}
|
|
3049
3049
|
// package.json
|
|
3050
|
-
var version = "0.0.
|
|
3050
|
+
var version = "0.0.410";
|
|
3051
3051
|
|
|
3052
3052
|
// bin/cli.ts
|
|
3053
3053
|
var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
|
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screens run on zig-js, and only on zig-js. zig-js exports JavaScriptCore's C
|
|
3
|
+
* API under the same names, and a host built with `webkit` sits one framework
|
|
4
|
+
* away from Apple's JavaScriptCore. So a `JS*` function zig-js does not define,
|
|
5
|
+
* or a Swift import that autolinks JavaScriptCore, would quietly bind part of
|
|
6
|
+
* the screen's engine to the system one, and fail far from the cause (a value
|
|
7
|
+
* from one engine handed to the other). The build checks the linked host.
|
|
8
|
+
*/
|
|
9
|
+
/** What in a host's imports would run on the system's JavaScriptCore; empty when everything is zig-js. */
|
|
10
|
+
export declare function systemEngineUse(undefinedSymbols: string[], linkedLibraries: string[]): string[];
|
|
11
|
+
/** Fails when the linked host would run any script on the system's JavaScriptCore. */
|
|
12
|
+
export declare function assertZigJsEngine(executable: string): Promise<void>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { assertZigJsEngine, systemEngineUse } from './engine.js';
|
|
1
2
|
export { macosToolchain, pantryExecutable, resolveMacOSToolchain, resolveZig, toolchainCacheDir } from './toolchain.js';
|
|
2
3
|
export type { MacOSToolchain } from './toolchain.js';
|
|
3
4
|
export interface MacOSNativeBuildOptions {
|
package/dist/macos/src/index.js
CHANGED
|
@@ -5,6 +5,36 @@ import { existsSync as existsSync2 } from "fs";
|
|
|
5
5
|
import { join as join2, resolve } from "path";
|
|
6
6
|
import process2 from "process";
|
|
7
7
|
|
|
8
|
+
// ../macos/src/engine.ts
|
|
9
|
+
var SYSTEM_ENGINE_SYMBOL = /^_(?:kJS[A-Z]|JS(?:Value|Object|String|BigInt|Context|GlobalContext|Class|PropertyName|EvaluateScript|CheckScriptSyntax|GarbageCollect|Script|Weak|ReportExtraMemoryCost|Synchronous)|OBJC_CLASS_\$_JS(?:Context|Value|ManagedValue|VirtualMachine)$)/;
|
|
10
|
+
function systemEngineUse(undefinedSymbols, linkedLibraries) {
|
|
11
|
+
return [
|
|
12
|
+
...linkedLibraries.filter((library) => library.includes("/JavaScriptCore.framework/")),
|
|
13
|
+
...undefinedSymbols.filter((symbol) => SYSTEM_ENGINE_SYMBOL.test(symbol))
|
|
14
|
+
];
|
|
15
|
+
}
|
|
16
|
+
async function lines(args) {
|
|
17
|
+
const child = Bun.spawn(args, { stdout: "pipe", stderr: "inherit" });
|
|
18
|
+
const text = await new Response(child.stdout).text();
|
|
19
|
+
if (await child.exited !== 0)
|
|
20
|
+
throw new Error(`Could not inspect the host: ${args.join(" ")}`);
|
|
21
|
+
return text.split(`
|
|
22
|
+
`).map((line) => line.trim()).filter(Boolean);
|
|
23
|
+
}
|
|
24
|
+
async function assertZigJsEngine(executable) {
|
|
25
|
+
const [undefinedSymbols, linked] = await Promise.all([
|
|
26
|
+
lines(["xcrun", "nm", "-u", "-j", executable]),
|
|
27
|
+
lines(["xcrun", "otool", "-L", executable])
|
|
28
|
+
]);
|
|
29
|
+
const leaks = systemEngineUse(undefinedSymbols, linked.slice(1).map((line) => line.split(" (")[0]));
|
|
30
|
+
if (leaks.length) {
|
|
31
|
+
throw new Error(`The host would run scripts on the system's JavaScriptCore instead of zig-js:
|
|
32
|
+
${leaks.join(`
|
|
33
|
+
`)}
|
|
34
|
+
Define the missing functions in zig-js, or keep JavaScriptCore out of the Swift imports.`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
8
38
|
// ../macos/src/toolchain.ts
|
|
9
39
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
10
40
|
import { homedir } from "os";
|
|
@@ -157,16 +187,19 @@ async function buildMacOSNative(options) {
|
|
|
157
187
|
"-o",
|
|
158
188
|
executable
|
|
159
189
|
]);
|
|
190
|
+
await assertZigJsEngine(executable);
|
|
160
191
|
const bundle = join2(outdir, "native-screen.js");
|
|
161
192
|
if (resolve(options.nativeBundle) !== bundle)
|
|
162
193
|
await cp(options.nativeBundle, bundle);
|
|
163
194
|
return { executable, bundle };
|
|
164
195
|
}
|
|
165
196
|
export {
|
|
197
|
+
assertZigJsEngine,
|
|
166
198
|
buildMacOSNative,
|
|
167
199
|
macosToolchain,
|
|
168
200
|
pantryExecutable,
|
|
169
201
|
resolveMacOSToolchain,
|
|
170
202
|
resolveZig,
|
|
203
|
+
systemEngineUse,
|
|
171
204
|
toolchainCacheDir
|
|
172
205
|
};
|
|
@@ -16,12 +16,17 @@ final class CraftMacScene: NSObject, NSWindowDelegate {
|
|
|
16
16
|
let window: NSWindow
|
|
17
17
|
let runtime: CraftMacRuntime
|
|
18
18
|
let renderer: CraftMacRenderer
|
|
19
|
+
/// Where the close button's top-left corner sits, from the window's top-left (`--traffic-lights 18,18`):
|
|
20
|
+
/// a browser lines the buttons up with its own toolbar row. Nil keeps AppKit's place.
|
|
21
|
+
let trafficLights: CGPoint?
|
|
19
22
|
init(name: String, route: String, options: [String: Any], application: CraftMacApplication) {
|
|
20
23
|
self.name = name
|
|
21
24
|
runtime = CraftMacRuntime(route: route, params: options["params"] as? [String: Any] ?? [:], network: application.network)
|
|
22
25
|
renderer = CraftMacRenderer(runtime: runtime)
|
|
23
26
|
let width = number(options["width"]) ?? 1120, height = number(options["height"]) ?? 740
|
|
24
27
|
let transparent = (options["titlebarStyle"] as? String ?? application.titlebarStyle) == "transparent"
|
|
28
|
+
let lights = options["trafficLights"] as? [Any] ?? (name == "main" ? application.trafficLights : nil)
|
|
29
|
+
trafficLights = lights.flatMap { values in values.count == 2 ? number(values[0]).flatMap { x in number(values[1]).map { CGPoint(x: x, y: $0) } } : nil }
|
|
25
30
|
let panel = CraftMacWindow(contentRect: CGRect(x: 0, y: 0, width: width, height: height), styleMask: transparent ? [.titled, .closable, .resizable, .miniaturizable, .fullSizeContentView] : [.titled, .closable, .resizable, .miniaturizable], backing: .buffered, defer: false)
|
|
26
31
|
panel.runtime = runtime
|
|
27
32
|
window = panel
|
|
@@ -39,8 +44,24 @@ final class CraftMacScene: NSObject, NSWindowDelegate {
|
|
|
39
44
|
window.delegate = self; window.center()
|
|
40
45
|
if name == "main" && application.testDirectory == nil { window.setFrameAutosaveName("\(application.appName)NativeMain") }
|
|
41
46
|
window.makeKeyAndOrderFront(nil)
|
|
47
|
+
placeTrafficLights()
|
|
42
48
|
runtime.load(application.bundle)
|
|
43
49
|
}
|
|
50
|
+
|
|
51
|
+
/// AppKit lays the title bar out again on resize, full screen and appearance changes, so this runs after each.
|
|
52
|
+
func placeTrafficLights() {
|
|
53
|
+
guard let inset = trafficLights, let close = window.standardWindowButton(.closeButton), let titlebar = close.superview?.superview else { return }
|
|
54
|
+
let buttons: [NSWindow.ButtonType] = [.closeButton, .miniaturizeButton, .zoomButton]
|
|
55
|
+
let size = close.frame.size
|
|
56
|
+
let height = size.height + inset.y * 2
|
|
57
|
+
titlebar.frame = CGRect(x: titlebar.frame.minX, y: window.frame.height - height, width: titlebar.frame.width, height: height)
|
|
58
|
+
// The standard spacing: each button 6pt after the last.
|
|
59
|
+
for (index, type) in buttons.enumerated() {
|
|
60
|
+
window.standardWindowButton(type)?.setFrameOrigin(CGPoint(x: inset.x + CGFloat(index) * (size.width + 6), y: (height - size.height) / 2))
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
func windowDidResize(_ notification: Notification) { placeTrafficLights() }
|
|
64
|
+
func windowDidExitFullScreen(_ notification: Notification) { placeTrafficLights() }
|
|
44
65
|
func windowShouldClose(_ sender: NSWindow) -> Bool {
|
|
45
66
|
if name == "main" { sender.orderOut(nil); return false }
|
|
46
67
|
return true
|
|
@@ -64,6 +85,7 @@ final class CraftMacApplication: NSObject, NSApplicationDelegate, UNUserNotifica
|
|
|
64
85
|
var bundle = ""
|
|
65
86
|
var initialRoute = "Main"
|
|
66
87
|
var titlebarStyle = "standard"
|
|
88
|
+
var trafficLights: [Any]?
|
|
67
89
|
/// Apps that ship their own agent (Attic) hand out a session cookie at
|
|
68
90
|
/// `/native/session`. An app that talks to a hosted API has no such
|
|
69
91
|
/// endpoint and starts with `--no-session`.
|
|
@@ -121,6 +143,8 @@ final class CraftMacApplication: NSObject, NSApplicationDelegate, UNUserNotifica
|
|
|
121
143
|
let scheme = CraftMacColor.isDark ? "dark" : "light"
|
|
122
144
|
for scene in scenes.values {
|
|
123
145
|
scene.runtime.context.evaluateScript("__stxNativeBridge.colorScheme = '\(scheme)';")
|
|
146
|
+
// The protocol's own message: the screen restyles its `dark:` classes and tells `craft.appearance.onChange`.
|
|
147
|
+
scene.runtime.send("APPEARANCE", ["colorScheme": scheme])
|
|
124
148
|
scene.window.effectiveAppearance.performAsCurrentDrawingAppearance {
|
|
125
149
|
for box in scene.renderer.boxes.values {
|
|
126
150
|
box.layer?.backgroundColor = CraftMacColor.parse(box.style["backgroundColor"], fallback: .clear).cgColor
|
|
@@ -158,10 +182,19 @@ final class CraftMacApplication: NSObject, NSApplicationDelegate, UNUserNotifica
|
|
|
158
182
|
NSApp.mainMenu = bar; NSApp.windowsMenu = window
|
|
159
183
|
}
|
|
160
184
|
private func addMenu(_ menu: NSMenu, to bar: NSMenu) { let item = NSMenuItem(title: menu.title, action: nil, keyEquivalent: ""); item.submenu = menu; bar.addItem(item) }
|
|
185
|
+
/// Keys a shortcut names rather than types (`ctrl+tab`, `cmd+alt+right`), as AppKit's key equivalents spell them.
|
|
186
|
+
static let namedKeys: [String: String] = {
|
|
187
|
+
var keys = ["up": "\u{F700}", "down": "\u{F701}", "left": "\u{F702}", "right": "\u{F703}",
|
|
188
|
+
"tab": "\t", "return": "\r", "enter": "\r", "escape": "\u{1B}", "esc": "\u{1B}", "space": " ",
|
|
189
|
+
"backspace": "\u{08}", "delete": "\u{08}", "forwarddelete": "\u{F728}",
|
|
190
|
+
"home": "\u{F729}", "end": "\u{F72B}", "pageup": "\u{F72C}", "pagedown": "\u{F72D}", "plus": "+", "minus": "-"]
|
|
191
|
+
for number in 1...12 { keys["f\(number)"] = String(UnicodeScalar(0xF703 + number)!) }
|
|
192
|
+
return keys
|
|
193
|
+
}()
|
|
161
194
|
private func menuItem(_ raw: [String: Any], target: AnyObject, action: Selector) -> NSMenuItem {
|
|
162
195
|
if raw["separator"] as? Bool == true { return .separator() }
|
|
163
196
|
let parts = (raw["shortcut"] as? String ?? "").lowercased().split(separator: "+").map(String.init)
|
|
164
|
-
let key = [
|
|
197
|
+
let key = Self.namedKeys[parts.last ?? ""] ?? parts.last ?? ""
|
|
165
198
|
let item = NSMenuItem(title: raw["label"] as? String ?? raw["title"] as? String ?? "", action: action, keyEquivalent: key)
|
|
166
199
|
item.target = target; item.representedObject = raw["id"]
|
|
167
200
|
item.keyEquivalentModifierMask = []
|
|
@@ -302,6 +335,26 @@ final class CraftMacApplication: NSObject, NSApplicationDelegate, UNUserNotifica
|
|
|
302
335
|
scene.renderer.root.layoutSubtreeIfNeeded()
|
|
303
336
|
return ["ok": true]
|
|
304
337
|
}
|
|
338
|
+
// A control by its testID, driven the way a person would: pressed, typed into, submitted, a key, the pointer over it.
|
|
339
|
+
if ["press", "type", "submit", "key", "hover"].contains(kind) {
|
|
340
|
+
guard let target = action["target"] as? String,
|
|
341
|
+
let box = scene.renderer.boxes.values.first(where: { ($0.props["testID"] as? String ?? $0.props["testId"] as? String ?? $0.id) == target }) else { return ["ok": false, "error": "No control \(action["target"] ?? "")"] }
|
|
342
|
+
let text = action["text"] as? String ?? ""
|
|
343
|
+
switch kind {
|
|
344
|
+
case "press": if let button = box.control as? NSButton { button.performClick(nil) } else { box.fire("onPress") }
|
|
345
|
+
case "type":
|
|
346
|
+
if let field = box.control as? NSTextField { field.stringValue = text }
|
|
347
|
+
else if let view = (box.control as? NSScrollView)?.documentView as? NSTextView { view.string = text }
|
|
348
|
+
box.input(text)
|
|
349
|
+
case "submit": box.fire("onSubmitEditing", ["text": text, "value": text, "nativeEvent": ["text": text]])
|
|
350
|
+
case "key": box.fire("onKeyPress", ["key": action["key"] as? String ?? "", "shiftKey": action["shift"] as? Bool == true, "text": text])
|
|
351
|
+
default:
|
|
352
|
+
let hovered = action["value"] as? Bool ?? true
|
|
353
|
+
box.hovering = hovered; box.applyAppearance(animated: false)
|
|
354
|
+
box.fire("onHoverChange", ["hovered": hovered])
|
|
355
|
+
}
|
|
356
|
+
return ["ok": true]
|
|
357
|
+
}
|
|
305
358
|
if kind == "screenshot" {
|
|
306
359
|
guard let directory = testDirectory, let view = scene.window.contentView?.superview,
|
|
307
360
|
let bitmap = view.bitmapImageRepForCachingDisplay(in: view.bounds) else { return ["ok": false, "error": "Window cannot be rendered"] }
|
|
@@ -397,6 +450,9 @@ final class CraftMacApplication: NSObject, NSApplicationDelegate, UNUserNotifica
|
|
|
397
450
|
application.appName = argument("--title", key: "title", fallback: "Craft")
|
|
398
451
|
application.initialRoute = argument("--route", key: "route", fallback: "Main")
|
|
399
452
|
application.titlebarStyle = argument("--titlebar-style", key: "titlebarStyle", fallback: "standard")
|
|
453
|
+
// `18,18` on the command line; `[18, 18]` in craft-app.json.
|
|
454
|
+
let lights = argument("--traffic-lights", key: "trafficLights").split(separator: ",").compactMap { Double($0.trimmingCharacters(in: CharacterSet(charactersIn: " []"))) }
|
|
455
|
+
if lights.count == 2 { application.trafficLights = lights }
|
|
400
456
|
application.requiresSession = !CommandLine.arguments.contains("--no-session") && packaged["session"] as? Bool != false
|
|
401
457
|
for (flag, key) in [("--width", "width"), ("--height", "height"), ("--min-width", "minWidth"), ("--min-height", "minHeight")] {
|
|
402
458
|
if let value = Double(argument(flag, key: key)), value.isFinite, value > 0 { application.mainWindowOptions[key] = value }
|
|
@@ -276,6 +276,7 @@ final class CraftMacBox: NSView, NSTextFieldDelegate, NSTextViewDelegate, NSDrag
|
|
|
276
276
|
var downPoint: NSPoint?
|
|
277
277
|
var lastDropPosition = ""
|
|
278
278
|
var acceptsDrops = false
|
|
279
|
+
var wantedFocus = false
|
|
279
280
|
var componentRoot: CraftMacBox? { props["data-stx-component-wrapper"] != nil && flow?.boxes.count == 1 && flow?.boxes.first?.kind != "Modal" ? flow?.boxes.first : nil }
|
|
280
281
|
var layoutStyle: [String: Any] {
|
|
281
282
|
guard let child = componentRoot else { return style }
|
|
@@ -366,8 +367,13 @@ final class CraftMacBox: NSView, NSTextFieldDelegate, NSTextViewDelegate, NSDrag
|
|
|
366
367
|
border.isHidden = (number(style["border\(edge)Width"]) ?? 0) <= 0
|
|
367
368
|
}
|
|
368
369
|
if let name = props["material"] as? String {
|
|
369
|
-
if material == nil { let effect = NSVisualEffectView(); effect.
|
|
370
|
-
|
|
370
|
+
if material == nil { let effect = NSVisualEffectView(); effect.state = .followsWindowActiveState; addSubview(effect, positioned: .below, relativeTo: control); material = effect }
|
|
371
|
+
// Chrome shows the desktop through it; a floating surface (a command bar, a popover) blurs the window's own content.
|
|
372
|
+
let materials: [String: NSVisualEffectView.Material] = ["sidebar": .sidebar, "header": .headerView, "window": .windowBackground, "titlebar": .titlebar,
|
|
373
|
+
"popover": .popover, "menu": .menu, "hud": .hudWindow, "sheet": .sheet, "underPage": .underPageBackground]
|
|
374
|
+
material?.material = materials[name] ?? .headerView
|
|
375
|
+
material?.blendingMode = ["popover", "menu", "hud", "sheet"].contains(name) ? .withinWindow : .behindWindow
|
|
376
|
+
if name == "hud" { material?.state = .active }
|
|
371
377
|
} else { material?.removeFromSuperview(); material = nil }
|
|
372
378
|
let identifier = props["testID"] as? String ?? props["testId"] as? String ?? props["accessibilityIdentifier"] as? String ?? id
|
|
373
379
|
control?.setAccessibilityIdentifier(identifier)
|
|
@@ -405,7 +411,19 @@ final class CraftMacBox: NSView, NSTextFieldDelegate, NSTextViewDelegate, NSDrag
|
|
|
405
411
|
if props["variant"] as? String == "primary" { button.bezelColor = .controlAccentColor }
|
|
406
412
|
if props["variant"] as? String == "danger" { button.contentTintColor = .systemRed }
|
|
407
413
|
}
|
|
408
|
-
if kind == "TextInput" {
|
|
414
|
+
if kind == "TextInput" {
|
|
415
|
+
configureInput(font: font)
|
|
416
|
+
// `autoFocus` takes the keyboard when it turns on (a command bar opening); `selectTextOnFocus` then selects it all.
|
|
417
|
+
let focus = props["autoFocus"] as? Bool == true
|
|
418
|
+
if focus && !wantedFocus {
|
|
419
|
+
DispatchQueue.main.async { [weak self] in
|
|
420
|
+
guard let self, let window = self.window, let target = (self.control as? NSScrollView)?.documentView ?? self.control else { return }
|
|
421
|
+
window.makeFirstResponder(target)
|
|
422
|
+
if self.props["selectTextOnFocus"] as? Bool == true { (target as? NSTextField)?.currentEditor()?.selectAll(nil) ?? (target as? NSTextView)?.selectAll(nil) }
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
wantedFocus = focus
|
|
426
|
+
}
|
|
409
427
|
if let picker = control as? NSPopUpButton {
|
|
410
428
|
let rawOptions = props["options"] as? [[String: Any]] ?? (node["children"] as? [[String: Any]] ?? []).map { child in
|
|
411
429
|
["value": (child["props"] as? [String: Any])?["value"] ?? nativeText(child), "label": nativeText(child)]
|
|
@@ -489,8 +507,16 @@ final class CraftMacBox: NSView, NSTextFieldDelegate, NSTextViewDelegate, NSDrag
|
|
|
489
507
|
text.setAccessibilityLabel(props["placeholder"] as? String ?? "Message")
|
|
490
508
|
} else if let field = control as? NSTextField {
|
|
491
509
|
if field.stringValue != value { field.stringValue = value }
|
|
492
|
-
field.placeholderString = props["placeholder"] as? String
|
|
493
510
|
field.font = font; field.isEnabled = props["disabled"] as? Bool != true
|
|
511
|
+
// A field the style colours (a light address pill in a dark window) follows it, placeholder included.
|
|
512
|
+
if style["color"] != nil {
|
|
513
|
+
let color = CraftMacColor.parse(style["color"])
|
|
514
|
+
field.textColor = color
|
|
515
|
+
field.placeholderAttributedString = (props["placeholder"] as? String).map { NSAttributedString(string: $0, attributes: [.foregroundColor: color.withAlphaComponent(0.5), .font: font]) }
|
|
516
|
+
} else {
|
|
517
|
+
field.textColor = .labelColor
|
|
518
|
+
field.placeholderString = props["placeholder"] as? String
|
|
519
|
+
}
|
|
494
520
|
if let bordered = props["bordered"] as? Bool { field.isBordered = bordered; field.drawsBackground = bordered }
|
|
495
521
|
field.setAccessibilityIdentifier(props["testID"] as? String ?? props["testId"] as? String ?? id)
|
|
496
522
|
}
|
|
@@ -720,6 +746,16 @@ final class CraftMacBox: NSView, NSTextFieldDelegate, NSTextViewDelegate, NSDrag
|
|
|
720
746
|
control?.frame = CGRect(x: padding.left, y: padding.top, width: max(0, bounds.width - padding.left - padding.right), height: max(0, bounds.height - padding.top - padding.bottom))
|
|
721
747
|
} else { control?.frame = bounds }
|
|
722
748
|
material?.frame = bounds
|
|
749
|
+
// A visual effect view ignores its layer's corner radius; a stretchable mask rounds it (as the tray popover does).
|
|
750
|
+
if let material {
|
|
751
|
+
let radius = layer?.cornerRadius ?? 0
|
|
752
|
+
if radius > 0, material.maskImage?.capInsets.top != radius {
|
|
753
|
+
let edge = radius * 2 + 1
|
|
754
|
+
let mask = NSImage(size: CGSize(width: edge, height: edge), flipped: false) { rect in NSBezierPath(roundedRect: rect, xRadius: radius, yRadius: radius).fill(); return true }
|
|
755
|
+
mask.capInsets = NSEdgeInsets(top: radius, left: radius, bottom: radius, right: radius); mask.resizingMode = .stretch
|
|
756
|
+
material.maskImage = mask
|
|
757
|
+
} else if radius == 0 { material.maskImage = nil }
|
|
758
|
+
}
|
|
723
759
|
gradient?.frame = bounds
|
|
724
760
|
gradient?.cornerRadius = layer?.cornerRadius ?? 0
|
|
725
761
|
// An explicit path spares Core Animation an offscreen pass to find the shadow's shape, every frame.
|
|
@@ -782,6 +818,12 @@ final class CraftMacBox: NSView, NSTextFieldDelegate, NSTextViewDelegate, NSDrag
|
|
|
782
818
|
renderer?.runtime.send("CRAFT_EVENT", ["event": "craftDesktopCommand", "detail": ["id": "escape"]]); return true
|
|
783
819
|
}
|
|
784
820
|
if commandSelector == #selector(NSResponder.insertNewline(_:)), events["onSubmitEditing"] != nil { fire("onSubmitEditing", ["text": textView.string, "value": textView.string, "nativeEvent": ["text": textView.string]]); return true }
|
|
821
|
+
// The keys a list driven from a field needs (a command bar, an address bar's suggestions), in the web's names.
|
|
822
|
+
let keys: [Selector: String] = [#selector(NSResponder.moveUp(_:)): "ArrowUp", #selector(NSResponder.moveDown(_:)): "ArrowDown",
|
|
823
|
+
#selector(NSResponder.insertTab(_:)): "Tab", #selector(NSResponder.insertBacktab(_:)): "Tab"]
|
|
824
|
+
if let key = keys[commandSelector], events["onKeyPress"] != nil {
|
|
825
|
+
fire("onKeyPress", ["key": key, "shiftKey": commandSelector == #selector(NSResponder.insertBacktab(_:)), "text": textView.string]); return true
|
|
826
|
+
}
|
|
785
827
|
return false
|
|
786
828
|
}
|
|
787
829
|
func controlTextDidEndEditing(_ notification: Notification) {
|