craft-native 0.0.185 → 0.0.187
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 +235 -114
- package/dist/index.cjs +178 -35
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +272 -130
- package/dist/ios/src/index.js +8 -3
- package/dist/ios/templates/CraftNativeScreen.swift +20 -6
- package/dist/temp-dir.d.ts +65 -0
- package/package.json +1 -1
package/dist/ios/src/index.js
CHANGED
|
@@ -156,7 +156,12 @@ function serializeSimulatorSteps(steps) {
|
|
|
156
156
|
`;
|
|
157
157
|
}
|
|
158
158
|
function createNativeMutationValidator() {
|
|
159
|
-
const output = mkdtempSync(join2(tmpdir(),
|
|
159
|
+
const output = mkdtempSync(join2(tmpdir(), `craft-native-mutation-p${process.pid}-`));
|
|
160
|
+
const removeOutput = () => {
|
|
161
|
+
process.off("exit", removeOutput);
|
|
162
|
+
rmSync(output, { recursive: true, force: true });
|
|
163
|
+
};
|
|
164
|
+
process.on("exit", removeOutput);
|
|
160
165
|
const binary = join2(output, "validate");
|
|
161
166
|
try {
|
|
162
167
|
writeFileSync(join2(output, "main.swift"), readFileSync(join2(templates, "MutationValidator.swift")));
|
|
@@ -164,7 +169,7 @@ function createNativeMutationValidator() {
|
|
|
164
169
|
if (build.exitCode !== 0)
|
|
165
170
|
throw new Error(build.stderr.toString());
|
|
166
171
|
} catch (error) {
|
|
167
|
-
|
|
172
|
+
removeOutput();
|
|
168
173
|
throw error;
|
|
169
174
|
}
|
|
170
175
|
let disposed = false;
|
|
@@ -185,7 +190,7 @@ function createNativeMutationValidator() {
|
|
|
185
190
|
},
|
|
186
191
|
dispose() {
|
|
187
192
|
disposed = true;
|
|
188
|
-
|
|
193
|
+
removeOutput();
|
|
189
194
|
}
|
|
190
195
|
};
|
|
191
196
|
}
|
|
@@ -577,6 +577,9 @@ private final class CraftNativeVideoView: UIView, AVPlayerViewControllerDelegate
|
|
|
577
577
|
// A muted video makes no sound, so it leaves the session as it is.
|
|
578
578
|
if muted { releaseSession() } else { claimSession() }
|
|
579
579
|
playerController.showsPlaybackControls = controls
|
|
580
|
+
// The system's controls are for VoiceOver too; a bare video stays one element.
|
|
581
|
+
playerController.view.accessibilityElementsHidden = !controls
|
|
582
|
+
isUserInteractionEnabled = true
|
|
580
583
|
playerController.videoGravity = resizeMode == "cover" ? .resizeAspectFill
|
|
581
584
|
: resizeMode == "stretch" ? .resize
|
|
582
585
|
: .resizeAspect
|
|
@@ -5687,6 +5690,13 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
5687
5690
|
return handler
|
|
5688
5691
|
}
|
|
5689
5692
|
|
|
5693
|
+
/// Views that take their own touches whatever handlers the screen gives
|
|
5694
|
+
/// them: a scroll view, a modal, and a video's system controls (with
|
|
5695
|
+
/// interaction off, its play, scrubber and full screen never answered).
|
|
5696
|
+
private func ownsTouches(_ view: UIView) -> Bool {
|
|
5697
|
+
view is UIScrollView || view is CraftNativeModalView || view is CraftNativeVideoView
|
|
5698
|
+
}
|
|
5699
|
+
|
|
5690
5700
|
private func updatePressHandler(_ handler: String?, for view: UIView) {
|
|
5691
5701
|
let id = ObjectIdentifier(view)
|
|
5692
5702
|
let handler = handler?.isEmpty == false ? handler : nil
|
|
@@ -5701,7 +5711,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
5701
5711
|
}
|
|
5702
5712
|
// A container keeps receiving touches without a handler of its own,
|
|
5703
5713
|
// or every control inside it (and the screen root) would be dead.
|
|
5704
|
-
if !(view
|
|
5714
|
+
if !ownsTouches(view) { view.isUserInteractionEnabled = actionable || longPressHandlers[id] != nil || view is CraftNativeFlowView }
|
|
5705
5715
|
}
|
|
5706
5716
|
|
|
5707
5717
|
private func updatePressFeedback(_ pressIn: String?, _ pressOut: String?, for view: UIView) {
|
|
@@ -5710,7 +5720,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
5710
5720
|
if let pressOut, !pressOut.isEmpty { pressOutHandlers[id] = pressOut } else { pressOutHandlers.removeValue(forKey: id) }
|
|
5711
5721
|
guard pressInHandlers[id] != nil || pressOutHandlers[id] != nil else {
|
|
5712
5722
|
if let recognizer = pressFeedbackRecognizers.removeValue(forKey: id) { view.removeGestureRecognizer(recognizer) }
|
|
5713
|
-
if !(view
|
|
5723
|
+
if !ownsTouches(view) { view.isUserInteractionEnabled = handlers[id] != nil || linkTargets[id] != nil || longPressHandlers[id] != nil || view is CraftNativeFlowView }
|
|
5714
5724
|
return
|
|
5715
5725
|
}
|
|
5716
5726
|
if pressFeedbackRecognizers[id] == nil {
|
|
@@ -5722,14 +5732,14 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
5722
5732
|
pressFeedbackRecognizers[id] = recognizer
|
|
5723
5733
|
view.addGestureRecognizer(recognizer)
|
|
5724
5734
|
}
|
|
5725
|
-
if !(view
|
|
5735
|
+
if !ownsTouches(view) { view.isUserInteractionEnabled = true }
|
|
5726
5736
|
}
|
|
5727
5737
|
|
|
5728
5738
|
private func updateLongPressHandler(_ handler: String?, for view: UIView) {
|
|
5729
5739
|
let id = ObjectIdentifier(view)
|
|
5730
5740
|
if let handler, !handler.isEmpty {
|
|
5731
5741
|
longPressHandlers[id] = handler
|
|
5732
|
-
if !(view
|
|
5742
|
+
if !ownsTouches(view) { view.isUserInteractionEnabled = true }
|
|
5733
5743
|
if longPressRecognizers[id] == nil {
|
|
5734
5744
|
let recognizer = UILongPressGestureRecognizer(target: self, action: #selector(viewLongPressed(_:)))
|
|
5735
5745
|
longPressRecognizers[id] = recognizer
|
|
@@ -5740,7 +5750,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
5740
5750
|
if let recognizer = longPressRecognizers.removeValue(forKey: id) {
|
|
5741
5751
|
view.removeGestureRecognizer(recognizer)
|
|
5742
5752
|
}
|
|
5743
|
-
if !(view
|
|
5753
|
+
if !ownsTouches(view) { view.isUserInteractionEnabled = handlers[id] != nil || linkTargets[id] != nil || view is CraftNativeFlowView }
|
|
5744
5754
|
}
|
|
5745
5755
|
}
|
|
5746
5756
|
|
|
@@ -6336,10 +6346,14 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
6336
6346
|
// reads: its label names the group, as a tab strip or a pager's
|
|
6337
6347
|
// name, and never stands in for its content. As an element of its
|
|
6338
6348
|
// own, a labelled pager hid every page from VoiceOver.
|
|
6349
|
+
// A video with its system controls is a group too: VoiceOver reaches
|
|
6350
|
+
// play, the scrubber, skipping and full screen inside it.
|
|
6351
|
+
let controlledVideo = type == "Video" && (props["controls"] as? Bool) == true
|
|
6339
6352
|
let container = ["ScrollView", "FlatList", "SectionList"].contains(type)
|
|
6340
6353
|
|| ["tablist", "radiogroup", "list", "menu", "menubar", "toolbar"].contains(role ?? "")
|
|
6354
|
+
|| controlledVideo
|
|
6341
6355
|
view.isAccessibilityElement = !container && role != "none" && (view.accessibilityLabel != nil || role != nil || ["Text", "Button", "Link", "Checkbox", "Image", "ImageBackground", "Video", "TextInput", "Picker", "DatePicker", "Switch", "Slider"].contains(type))
|
|
6342
|
-
view.accessibilityContainerType = container && view.accessibilityLabel != nil ? .semanticGroup : .none
|
|
6356
|
+
view.accessibilityContainerType = container && (view.accessibilityLabel != nil || controlledVideo) ? .semanticGroup : .none
|
|
6343
6357
|
var traits: UIAccessibilityTraits = []
|
|
6344
6358
|
switch role ?? type.lowercased() {
|
|
6345
6359
|
case "tablist": traits.insert(.tabBar)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scratch directories that do not outlive the process that made them.
|
|
3
|
+
*
|
|
4
|
+
* Every Craft temp directory goes through here: packaging, signing, the
|
|
5
|
+
* updater's staging area, the sidebar config spill file, and the build and
|
|
6
|
+
* test harnesses. Issue #357: a bare `mkdtempSync(join(tmpdir(), 'craft-…'))`
|
|
7
|
+
* per run, with no cleanup or a `finally` that a killed run never reaches,
|
|
8
|
+
* left 1,983 directories (~13 GB) in the macOS per-user temp folder.
|
|
9
|
+
*
|
|
10
|
+
* Three layers, because each one misses something the next one catches:
|
|
11
|
+
*
|
|
12
|
+
* 1. The caller removes the directory when it is done (`withTempDir`, or
|
|
13
|
+
* `removeTempDir` in a `finally`).
|
|
14
|
+
* 2. Every live directory is also removed on process exit and on SIGINT,
|
|
15
|
+
* SIGTERM and SIGHUP, which is what a Ctrl-C, a CI timeout or a tool's
|
|
16
|
+
* kill sends. Node skips `exit` listeners on a signal's default action,
|
|
17
|
+
* so the signals need their own handlers.
|
|
18
|
+
* 3. SIGKILL cannot be caught, and a synchronous child (`Bun.spawnSync`
|
|
19
|
+
* running xcodebuild) can hold the event loop until the parent is killed
|
|
20
|
+
* for good. So each name carries the owning pid (`craft-ios-p4242-a1B2c3`),
|
|
21
|
+
* and the first directory a later process creates sweeps up any whose
|
|
22
|
+
* owner is gone.
|
|
23
|
+
*/
|
|
24
|
+
/** Remove every directory this process created and has not removed yet. */
|
|
25
|
+
export declare function removeAllTempDirs(): void;
|
|
26
|
+
export interface SweepOptions {
|
|
27
|
+
/** Directory to sweep. Defaults to the OS temp directory. */
|
|
28
|
+
root?: string;
|
|
29
|
+
/** Whether a pid still runs. Injected by tests. */
|
|
30
|
+
isAlive?: (pid: number) => boolean;
|
|
31
|
+
/** Minimum age before an orphan is removed. */
|
|
32
|
+
minAgeMs?: number;
|
|
33
|
+
now?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Remove Craft temp directories whose owning process has exited.
|
|
37
|
+
*
|
|
38
|
+
* Only names this module produced are considered (`craft-…-p<pid>-XXXXXX`),
|
|
39
|
+
* so another tool's `craft-something` directory is never touched. Returns
|
|
40
|
+
* the paths it removed.
|
|
41
|
+
*/
|
|
42
|
+
export declare function sweepOrphanedTempDirs(options?: SweepOptions): string[];
|
|
43
|
+
/**
|
|
44
|
+
* The mkdtemp prefix for a label: `ios-native` and `craft-ios-native-` both
|
|
45
|
+
* become `craft-ios-native-p<pid>-`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function tempDirPrefix(label: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* Create a scratch directory in the OS temp folder that is removed when this
|
|
50
|
+
* process ends, however it ends. Remove it sooner with `removeTempDir`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function createTempDir(label: string, root?: string): string;
|
|
53
|
+
/** Remove a directory from `createTempDir` now rather than at exit. */
|
|
54
|
+
export declare function removeTempDir(dir: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* Let a directory from `createTempDir` outlive the process, for a harness
|
|
57
|
+
* asked to keep its generated project for inspection. The caller now owns
|
|
58
|
+
* it, and its pid-stamped name means a later sweep removes it once this
|
|
59
|
+
* process is gone.
|
|
60
|
+
*/
|
|
61
|
+
export declare function keepTempDir(dir: string): void;
|
|
62
|
+
/** Run `fn` with a fresh scratch directory that is removed when it settles. */
|
|
63
|
+
export declare function withTempDir<T>(label: string, fn: (dir: string) => T | Promise<T>): Promise<T>;
|
|
64
|
+
/** Directories created here and not yet removed. For tests and diagnostics. */
|
|
65
|
+
export declare function liveTempDirs(): string[];
|