@tastic/hud 0.23.0 → 0.24.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/README.md +59 -1
- package/dist/chunk-3TCJHMJP.mjs +14 -0
- package/dist/index.d.mts +2 -4
- package/dist/index.d.ts +2 -4
- package/dist/index.js +94 -104
- package/dist/index.mjs +8 -11
- package/dist/skia/index.d.mts +5 -0
- package/dist/skia/index.d.ts +5 -0
- package/dist/{SkiaGate.js → skia/index.js} +17 -21
- package/dist/skia/index.mjs +8 -0
- package/package.json +7 -7
- package/src/TriggerGaugeHost.tsx +20 -14
- package/src/guide/index.ts +1 -1
- package/src/index.ts +2 -2
- package/src/loadSkiaWeb.web.ts +7 -4
- package/src/locateSkiaWebFile.ts +16 -0
- package/src/skia/index.ts +12 -0
- package/dist/SkiaGate.d.mts +0 -13
- package/dist/SkiaGate.d.ts +0 -13
- package/dist/SkiaGate.mjs +0 -6
- package/dist/chunk-XEOFR236.mjs +0 -21
- package/src/SkiaGate.tsx +0 -45
- package/src/SkiaGate.web.tsx +0 -9
package/README.md
CHANGED
|
@@ -668,6 +668,64 @@ type-checks hud's source and needs it installed. `AchievementUnlockList` isn't h
|
|
|
668
668
|
Metro resolves both entries to the same source files, so an app that imports some components from here and
|
|
669
669
|
others from `@tastic/hud` still bundles each module once.
|
|
670
670
|
|
|
671
|
+
## Skia on web (`@tastic/hud/skia`)
|
|
672
|
+
|
|
673
|
+
On web, Skia draws through CanvasKit, a WASM file fetched at runtime. `@shopify/react-native-skia` builds
|
|
674
|
+
its `Skia` object from CanvasKit when the module is first evaluated, so a component that imports Skia can't
|
|
675
|
+
even be imported until CanvasKit has loaded. On native there is nothing to wait for.
|
|
676
|
+
|
|
677
|
+
Every app in the fleet loads Skia the same way, with a host pair: two small files per Skia component, side
|
|
678
|
+
by side, that Metro picks between by platform.
|
|
679
|
+
|
|
680
|
+
```tsx
|
|
681
|
+
// BoardHost.tsx: native renders the board directly, on the first frame
|
|
682
|
+
import { Board, type BoardProps } from './Board'
|
|
683
|
+
|
|
684
|
+
export default function BoardHost(props: BoardProps) {
|
|
685
|
+
return <Board {...props} />
|
|
686
|
+
}
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
```tsx
|
|
690
|
+
// BoardHost.web.tsx: web imports the board only once CanvasKit has loaded
|
|
691
|
+
import { loadSkiaWeb } from '@tastic/hud/skia'
|
|
692
|
+
import { lazy, Suspense } from 'react'
|
|
693
|
+
|
|
694
|
+
import type { BoardProps } from './Board'
|
|
695
|
+
|
|
696
|
+
const LazyBoard = lazy(() => loadSkiaWeb().then(() => import('./Board').then((m) => ({ default: m.Board }))))
|
|
697
|
+
|
|
698
|
+
export default function BoardHost(props: BoardProps) {
|
|
699
|
+
return (
|
|
700
|
+
<Suspense fallback={null}>
|
|
701
|
+
<LazyBoard {...props} />
|
|
702
|
+
</Suspense>
|
|
703
|
+
)
|
|
704
|
+
}
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
- Import the host, never `Board` itself, everywhere else in the app.
|
|
708
|
+
- Keep `lazy()` at module scope, as above, so the component is created once.
|
|
709
|
+
- Import from `@tastic/hud/skia` (also exported from `@tastic/hud`). It carries none of the main entry's
|
|
710
|
+
heavier peers, so it works in every app.
|
|
711
|
+
- A test that imports a native host imports Skia, which throws under Jest (no native bindings). Mock the
|
|
712
|
+
host, or don't import the screen that uses it.
|
|
713
|
+
|
|
714
|
+
`loadSkiaWeb()` fetches the app's `public/canvaskit.wasm` from `<baseUrl>/canvaskit.wasm`: under the Expo base
|
|
715
|
+
URL (`experiments.baseUrl`, e.g. `/snake/canvaskit.wasm`) when the app has one, and `/canvaskit.wasm` when it
|
|
716
|
+
doesn't. There's nothing to wire up: babel-preset-expo inlines `process.env.EXPO_BASE_URL` into hud's source
|
|
717
|
+
when Metro bundles it, the same as in the app's own code. It's idempotent, so every host can call it.
|
|
718
|
+
|
|
719
|
+
That rule is `locateSkiaWebFile(file: string): string`, also exported from `@tastic/hud/skia`, for code that
|
|
720
|
+
calls Skia's own `LoadSkiaWeb` or `WithSkiaWeb` (`opts={{ locateFile: locateSkiaWebFile }}`). Without it,
|
|
721
|
+
canvaskit looks for the file next to the running script or the page, which under a base URL is the wrong
|
|
722
|
+
place: from `/snake` (no trailing slash) it asks for `/canvaskit.wasm`, and a static-output app can end up
|
|
723
|
+
asking under `/_expo/static/js/web/`. `LoadSkiaWeb` also keeps its first call's result, a failed load
|
|
724
|
+
included, for the rest of the page's life.
|
|
725
|
+
|
|
726
|
+
`TriggerGaugeHost` uses the web half on both platforms: the main entry can't import Skia up front, since every
|
|
727
|
+
app that imports `@tastic/hud` would then load Skia, including in its tests.
|
|
728
|
+
|
|
671
729
|
## Install
|
|
672
730
|
|
|
673
731
|
Published to the public npm registry as `@tastic/hud`. Everything described above, including the
|
|
@@ -710,5 +768,5 @@ resolve module".
|
|
|
710
768
|
| `@rific/updater` | >=0.4.0, optional | main entry and `./chrome`: `BaseSettingsDialog`, `UpdateDialog` |
|
|
711
769
|
| `@tastic/profile` | >=0.4.0, optional | main entry: `PlayerSetupPanel`, `AchievementUnlockList` (`ProfileChip`) |
|
|
712
770
|
| `@tastic/split-screen` | >=0.5.0, optional | main entry: `useZoneClampedAlign` |
|
|
713
|
-
| `@shopify/react-native-skia` | >=2.6.0, optional | `TriggerGaugeHost`'s gauge (
|
|
771
|
+
| `@shopify/react-native-skia` | >=2.6.0, optional | `TriggerGaugeHost`'s gauge (loaded when one mounts) and `./skia` |
|
|
714
772
|
| `@tastic/achievements` | >=0.2.0, optional | `AchievementCatalogSection`, `AchievementUnlockList`, type-only (`AchievementCatalogRow`, first exported in 0.2.0; `AchievementTier`); `./chrome` needs it installed only to type-check |
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/loadSkiaWeb.ts
|
|
2
|
+
function loadSkiaWeb() {
|
|
3
|
+
return Promise.resolve();
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// src/locateSkiaWebFile.ts
|
|
7
|
+
function locateSkiaWebFile(file) {
|
|
8
|
+
return `${process.env.EXPO_BASE_URL ?? ""}/${file}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
loadSkiaWeb,
|
|
13
|
+
locateSkiaWebFile
|
|
14
|
+
};
|
package/dist/index.d.mts
CHANGED
|
@@ -5,8 +5,8 @@ import { ReactNode } from 'react';
|
|
|
5
5
|
import { AchievementTier } from '@tastic/achievements';
|
|
6
6
|
import { Profile } from '@tastic/profile';
|
|
7
7
|
import { ViewStyle, StyleProp } from 'react-native';
|
|
8
|
+
export { loadSkiaWeb, locateSkiaWebFile } from './skia/index.mjs';
|
|
8
9
|
import { SeedColor } from '@rific/auto-paper';
|
|
9
|
-
export { SkiaGate, SkiaGateProps } from './SkiaGate.mjs';
|
|
10
10
|
import '@tastic/core';
|
|
11
11
|
|
|
12
12
|
interface UnlockedAchievement {
|
|
@@ -125,8 +125,6 @@ interface Props$2<T extends string> {
|
|
|
125
125
|
}
|
|
126
126
|
declare function LabeledDropdown<T extends string>({ id, host, options, value, onChange, color, dark, align: forcedAlign, alignOverride }: Props$2<T>): react.JSX.Element;
|
|
127
127
|
|
|
128
|
-
declare function loadSkiaWeb(): Promise<void>;
|
|
129
|
-
|
|
130
128
|
declare function getColorPopoverId(idPrefix: string): string;
|
|
131
129
|
declare function getProfilePopoverId(idPrefix: string): string;
|
|
132
130
|
declare function getCpuDifficultyPopoverId(idPrefix: string): string;
|
|
@@ -210,4 +208,4 @@ declare function useQuitConfirmation(hasProgress: () => boolean, onConfirmedBack
|
|
|
210
208
|
|
|
211
209
|
declare function useZoneClampedAlign(open: boolean, contentWidth: number, contentHeight: number, rotation?: PopoverRotation): ReturnType<typeof useAutoAlign>;
|
|
212
210
|
|
|
213
|
-
export { AchievementUnlockList, type AchievementUnlockOwner, AlignResult, ControlSchemePicker, type ControlSchemePickerProps, type CornerStatusBadgeSeat, CornerStatusBadges, type CornerStatusBadgesProps, LABELED_DROPDOWN_POPOVER_WIDTH, LabeledDropdown, type LabeledDropdownOption, type MenuOption, type MenuSection, type MultiSelectSection, type OrientationMode, PlayerSetupPanel, type PlayerSetupPanelProps, PopoverHost, PopoverRotation, type QuitConfirmation, ReadyButton, SectionedDropdown, SharedActionBand, type SingleSelectSection, TriggerGaugeHost, type TriggerGaugeProps, type UnlockedAchievement, getColorPopoverId, getCpuDifficultyPopoverId, getLabeledDropdownContentHeight, getProfilePopoverId,
|
|
211
|
+
export { AchievementUnlockList, type AchievementUnlockOwner, AlignResult, ControlSchemePicker, type ControlSchemePickerProps, type CornerStatusBadgeSeat, CornerStatusBadges, type CornerStatusBadgesProps, LABELED_DROPDOWN_POPOVER_WIDTH, LabeledDropdown, type LabeledDropdownOption, type MenuOption, type MenuSection, type MultiSelectSection, type OrientationMode, PlayerSetupPanel, type PlayerSetupPanelProps, PopoverHost, PopoverRotation, type QuitConfirmation, ReadyButton, SectionedDropdown, SharedActionBand, type SingleSelectSection, TriggerGaugeHost, type TriggerGaugeProps, type UnlockedAchievement, getColorPopoverId, getCpuDifficultyPopoverId, getLabeledDropdownContentHeight, getProfilePopoverId, useAutoAlign, useQuitConfirmation, useZoneClampedAlign };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { ReactNode } from 'react';
|
|
|
5
5
|
import { AchievementTier } from '@tastic/achievements';
|
|
6
6
|
import { Profile } from '@tastic/profile';
|
|
7
7
|
import { ViewStyle, StyleProp } from 'react-native';
|
|
8
|
+
export { loadSkiaWeb, locateSkiaWebFile } from './skia/index.js';
|
|
8
9
|
import { SeedColor } from '@rific/auto-paper';
|
|
9
|
-
export { SkiaGate, SkiaGateProps } from './SkiaGate.js';
|
|
10
10
|
import '@tastic/core';
|
|
11
11
|
|
|
12
12
|
interface UnlockedAchievement {
|
|
@@ -125,8 +125,6 @@ interface Props$2<T extends string> {
|
|
|
125
125
|
}
|
|
126
126
|
declare function LabeledDropdown<T extends string>({ id, host, options, value, onChange, color, dark, align: forcedAlign, alignOverride }: Props$2<T>): react.JSX.Element;
|
|
127
127
|
|
|
128
|
-
declare function loadSkiaWeb(): Promise<void>;
|
|
129
|
-
|
|
130
128
|
declare function getColorPopoverId(idPrefix: string): string;
|
|
131
129
|
declare function getProfilePopoverId(idPrefix: string): string;
|
|
132
130
|
declare function getCpuDifficultyPopoverId(idPrefix: string): string;
|
|
@@ -210,4 +208,4 @@ declare function useQuitConfirmation(hasProgress: () => boolean, onConfirmedBack
|
|
|
210
208
|
|
|
211
209
|
declare function useZoneClampedAlign(open: boolean, contentWidth: number, contentHeight: number, rotation?: PopoverRotation): ReturnType<typeof useAutoAlign>;
|
|
212
210
|
|
|
213
|
-
export { AchievementUnlockList, type AchievementUnlockOwner, AlignResult, ControlSchemePicker, type ControlSchemePickerProps, type CornerStatusBadgeSeat, CornerStatusBadges, type CornerStatusBadgesProps, LABELED_DROPDOWN_POPOVER_WIDTH, LabeledDropdown, type LabeledDropdownOption, type MenuOption, type MenuSection, type MultiSelectSection, type OrientationMode, PlayerSetupPanel, type PlayerSetupPanelProps, PopoverHost, PopoverRotation, type QuitConfirmation, ReadyButton, SectionedDropdown, SharedActionBand, type SingleSelectSection, TriggerGaugeHost, type TriggerGaugeProps, type UnlockedAchievement, getColorPopoverId, getCpuDifficultyPopoverId, getLabeledDropdownContentHeight, getProfilePopoverId,
|
|
211
|
+
export { AchievementUnlockList, type AchievementUnlockOwner, AlignResult, ControlSchemePicker, type ControlSchemePickerProps, type CornerStatusBadgeSeat, CornerStatusBadges, type CornerStatusBadgesProps, LABELED_DROPDOWN_POPOVER_WIDTH, LabeledDropdown, type LabeledDropdownOption, type MenuOption, type MenuSection, type MultiSelectSection, type OrientationMode, PlayerSetupPanel, type PlayerSetupPanelProps, PopoverHost, PopoverRotation, type QuitConfirmation, ReadyButton, SectionedDropdown, SharedActionBand, type SingleSelectSection, TriggerGaugeHost, type TriggerGaugeProps, type UnlockedAchievement, getColorPopoverId, getCpuDifficultyPopoverId, getLabeledDropdownContentHeight, getProfilePopoverId, useAutoAlign, useQuitConfirmation, useZoneClampedAlign };
|
package/dist/index.js
CHANGED
|
@@ -38,22 +38,22 @@ __export(TriggerGauge_exports, {
|
|
|
38
38
|
function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, dashWidth, startDeg = DEFAULT_START_DEG, endDeg = startDeg }) {
|
|
39
39
|
const totalDurationMs = MOUNT_DURATION_MS + Math.max(0, segments - 1) * MOUNT_STAGGER_MS;
|
|
40
40
|
const mountProgress = (0, import_react_native_reanimated.useSharedValue)(0);
|
|
41
|
-
(0,
|
|
41
|
+
(0, import_react8.useLayoutEffect)(() => {
|
|
42
42
|
mountProgress.value = (0, import_react_native_reanimated.withTiming)(1, { duration: totalDurationMs, easing: import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.cubic) });
|
|
43
43
|
}, []);
|
|
44
44
|
const rawSweepDeg = ((endDeg - startDeg) % 360 + 360) % 360;
|
|
45
45
|
const totalSweepDeg = rawSweepDeg === 0 ? 360 : rawSweepDeg;
|
|
46
46
|
const step = segments > 0 ? totalSweepDeg / segments : 0;
|
|
47
|
-
const centerDeg = (0,
|
|
47
|
+
const centerDeg = (0, import_react8.useCallback)((i) => startDeg + (i + 0.5) * step, [startDeg, step]);
|
|
48
48
|
const singleSelect = litIndices.length === 1;
|
|
49
49
|
const angleDeg = (0, import_react_native_reanimated.useSharedValue)(centerDeg(litIndices[0] ?? 0));
|
|
50
50
|
const litIndicesKey = [...litIndices].sort((a, b) => a - b).join(",");
|
|
51
|
-
const [transitionFrom, setTransitionFrom] = (0,
|
|
51
|
+
const [transitionFrom, setTransitionFrom] = (0, import_react8.useState)(litIndices);
|
|
52
52
|
const transitionFromKey = [...transitionFrom].sort((a, b) => a - b).join(",");
|
|
53
|
-
const lastKeyRef = (0,
|
|
54
|
-
const lastIndicesRef = (0,
|
|
53
|
+
const lastKeyRef = (0, import_react8.useRef)(litIndicesKey);
|
|
54
|
+
const lastIndicesRef = (0, import_react8.useRef)(litIndices);
|
|
55
55
|
const selectionProgress = (0, import_react_native_reanimated.useSharedValue)(1);
|
|
56
|
-
(0,
|
|
56
|
+
(0, import_react8.useLayoutEffect)(() => {
|
|
57
57
|
if (litIndicesKey !== lastKeyRef.current) {
|
|
58
58
|
if (singleSelect && lastIndicesRef.current.length === 1) {
|
|
59
59
|
angleDeg.value = (0, import_react_native_reanimated.withTiming)(centerDeg(litIndices[0]), { duration: SELECTION_TRANSITION_DURATION_MS, easing: import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.cubic) });
|
|
@@ -130,21 +130,21 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
|
|
|
130
130
|
height: canvasSize,
|
|
131
131
|
width: canvasSize
|
|
132
132
|
};
|
|
133
|
-
return /* @__PURE__ */ (0,
|
|
134
|
-
/* @__PURE__ */ (0,
|
|
135
|
-
/* @__PURE__ */ (0,
|
|
133
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native17.View, { style: [styles14.container, gaugeSizeStyle], pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native_skia.Canvas, { style: canvasSizeStyle, children: [
|
|
134
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_skia.Path, { path: unlitPath, style: "stroke", strokeWidth: UNLIT_THICKNESS, strokeCap: "round", color: mutedColor, opacity: 0.35 }),
|
|
135
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_skia.Path, { path: litPath, style: "stroke", strokeWidth: LIT_THICKNESS, strokeCap: "round", color: accentColor, opacity: 1 })
|
|
136
136
|
] }) });
|
|
137
137
|
}
|
|
138
|
-
var import_react_native_skia, import_core8,
|
|
138
|
+
var import_react_native_skia, import_core8, import_react8, import_react_native17, import_react_native_reanimated, import_jsx_runtime15, UNLIT_THICKNESS, LIT_THICKNESS, DASH_GAP_PX, ARC_GAP, DEFAULT_START_DEG, MIN_DASH_ANGLE_DEG, MOUNT_DURATION_MS, MOUNT_STAGGER_MS, SELECTION_TRANSITION_DURATION_MS, styles14;
|
|
139
139
|
var init_TriggerGauge = __esm({
|
|
140
140
|
"src/TriggerGauge.tsx"() {
|
|
141
141
|
"use strict";
|
|
142
142
|
import_react_native_skia = require("@shopify/react-native-skia");
|
|
143
143
|
import_core8 = require("@tastic/core");
|
|
144
|
-
|
|
144
|
+
import_react8 = require("react");
|
|
145
145
|
import_react_native17 = require("react-native");
|
|
146
146
|
import_react_native_reanimated = require("react-native-reanimated");
|
|
147
|
-
|
|
147
|
+
import_jsx_runtime15 = require("react/jsx-runtime");
|
|
148
148
|
UNLIT_THICKNESS = 3;
|
|
149
149
|
LIT_THICKNESS = 7;
|
|
150
150
|
DASH_GAP_PX = 9;
|
|
@@ -194,7 +194,6 @@ __export(index_exports, {
|
|
|
194
194
|
RotatedFrame: () => RotatedFrame,
|
|
195
195
|
SectionedDropdown: () => SectionedDropdown,
|
|
196
196
|
SharedActionBand: () => SharedActionBand,
|
|
197
|
-
SkiaGate: () => SkiaGate,
|
|
198
197
|
StaggeredWord: () => StaggeredWord,
|
|
199
198
|
StatRow: () => StatRow,
|
|
200
199
|
StatSection: () => StatSection,
|
|
@@ -209,6 +208,7 @@ __export(index_exports, {
|
|
|
209
208
|
getStaggeredWordDuration: () => getStaggeredWordDuration,
|
|
210
209
|
isCompactDialogHeight: () => isCompactDialogHeight,
|
|
211
210
|
loadSkiaWeb: () => loadSkiaWeb,
|
|
211
|
+
locateSkiaWebFile: () => locateSkiaWebFile,
|
|
212
212
|
overlayActionStyles: () => overlayActionStyles,
|
|
213
213
|
overlayStyles: () => overlayStyles,
|
|
214
214
|
useAutoAlign: () => useAutoAlign,
|
|
@@ -1114,29 +1114,19 @@ var styles13 = import_react_native16.StyleSheet.create({
|
|
|
1114
1114
|
}
|
|
1115
1115
|
});
|
|
1116
1116
|
|
|
1117
|
-
// src/
|
|
1118
|
-
var
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
let cancelled = false;
|
|
1124
|
-
getComponent().then((mod) => {
|
|
1125
|
-
if (!cancelled) setComponent(() => mod.default);
|
|
1126
|
-
});
|
|
1127
|
-
return () => {
|
|
1128
|
-
cancelled = true;
|
|
1129
|
-
};
|
|
1130
|
-
}, [getComponent]);
|
|
1131
|
-
if (!Component) return fallback;
|
|
1132
|
-
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Component, { ...componentProps });
|
|
1117
|
+
// src/TriggerGaugeHost.tsx
|
|
1118
|
+
var import_react9 = require("react");
|
|
1119
|
+
|
|
1120
|
+
// src/loadSkiaWeb.ts
|
|
1121
|
+
function loadSkiaWeb() {
|
|
1122
|
+
return Promise.resolve();
|
|
1133
1123
|
}
|
|
1134
1124
|
|
|
1135
1125
|
// src/TriggerGaugeHost.tsx
|
|
1136
|
-
var
|
|
1137
|
-
var
|
|
1126
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1127
|
+
var LazyTriggerGauge = (0, import_react9.lazy)(() => loadSkiaWeb().then(() => Promise.resolve().then(() => (init_TriggerGauge(), TriggerGauge_exports)).then((mod) => ({ default: mod.TriggerGauge }))));
|
|
1138
1128
|
function TriggerGaugeHost(props) {
|
|
1139
|
-
return /* @__PURE__ */ (0,
|
|
1129
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react9.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(LazyTriggerGauge, { ...props }) });
|
|
1140
1130
|
}
|
|
1141
1131
|
|
|
1142
1132
|
// src/useAutoAlign.ts
|
|
@@ -1188,7 +1178,7 @@ function useAutoAlign(open, contentWidth, contentHeight, rotation = 0) {
|
|
|
1188
1178
|
}
|
|
1189
1179
|
|
|
1190
1180
|
// src/SectionedDropdown.tsx
|
|
1191
|
-
var
|
|
1181
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1192
1182
|
var MENU_BORDER_WIDTH = 1;
|
|
1193
1183
|
var TRIGGER_SIZE = 44;
|
|
1194
1184
|
var MENU_MIN_WIDTH = 200;
|
|
@@ -1241,14 +1231,14 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
|
|
|
1241
1231
|
const anchorStyle = [styles15.anchor, open && styles15.anchorOpen];
|
|
1242
1232
|
const menuStyle = { backgroundColor: menuBg, borderColor: menuBorderColor, maxHeight };
|
|
1243
1233
|
const dividerStyle = { backgroundColor: menuBorderColor };
|
|
1244
|
-
return /* @__PURE__ */ (0,
|
|
1245
|
-
/* @__PURE__ */ (0,
|
|
1246
|
-
/* @__PURE__ */ (0,
|
|
1247
|
-
/* @__PURE__ */ (0,
|
|
1234
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react_native19.View, { style: anchorStyle, children: [
|
|
1235
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react_native19.View, { ref: triggerRef, collapsable: false, style: styles15.triggerBox, children: [
|
|
1236
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
|
|
1237
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_feedback_press4.IconButton, { icon: triggerIcon, iconColor: triggerIconColor, size: triggerIconSize, accessibilityLabel, onPress: () => host.toggle(id) })
|
|
1248
1238
|
] }),
|
|
1249
|
-
/* @__PURE__ */ (0,
|
|
1250
|
-
sectionIndex > 0 && /* @__PURE__ */ (0,
|
|
1251
|
-
section.kind === "single" ? /* @__PURE__ */ (0,
|
|
1239
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: menuBorderColor, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: TRIGGER_SIZE, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native19.ScrollView, { style: [styles15.menu, menuStyle], contentContainerStyle: styles15.menuContent, showsVerticalScrollIndicator: false, children: sections.map((section, sectionIndex) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react_native19.View, { children: [
|
|
1240
|
+
sectionIndex > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native19.View, { style: [styles15.divider, dividerStyle] }),
|
|
1241
|
+
section.kind === "single" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1252
1242
|
SingleSection,
|
|
1253
1243
|
{
|
|
1254
1244
|
section,
|
|
@@ -1261,7 +1251,7 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
|
|
|
1261
1251
|
if (autoDismiss) host.close();
|
|
1262
1252
|
}
|
|
1263
1253
|
}
|
|
1264
|
-
) : /* @__PURE__ */ (0,
|
|
1254
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
|
|
1265
1255
|
] }, section.id)) }) })
|
|
1266
1256
|
] });
|
|
1267
1257
|
}
|
|
@@ -1273,18 +1263,18 @@ function SingleSection({
|
|
|
1273
1263
|
labelFontFamily,
|
|
1274
1264
|
onSelect
|
|
1275
1265
|
}) {
|
|
1276
|
-
return /* @__PURE__ */ (0,
|
|
1266
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_jsx_runtime17.Fragment, { children: section.options.map((option) => {
|
|
1277
1267
|
const selected = option.value === section.value;
|
|
1278
1268
|
const taken = !selected && option.value === section.takenValue;
|
|
1279
1269
|
const rowColor = selected ? onAccentColor : mutedColor;
|
|
1280
1270
|
const itemStyle = [styles15.item, selected && { backgroundColor: accentColor }, taken && styles15.itemTaken];
|
|
1281
1271
|
const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
|
|
1282
1272
|
const descriptionStyle = { fontFamily: labelFontFamily, color: rowColor };
|
|
1283
|
-
return /* @__PURE__ */ (0,
|
|
1284
|
-
option.icon && /* @__PURE__ */ (0,
|
|
1285
|
-
/* @__PURE__ */ (0,
|
|
1286
|
-
/* @__PURE__ */ (0,
|
|
1287
|
-
option.description && /* @__PURE__ */ (0,
|
|
1273
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_feedback_press4.TouchableRipple, { disabled: taken, onPress: () => onSelect(option.value), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react_native19.View, { style: styles15.itemRow, children: [
|
|
1274
|
+
option.icon && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native_paper11.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
|
|
1275
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react_native19.View, { style: styles15.itemLabelColumn, children: [
|
|
1276
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native_paper11.Text, { variant: "labelLarge", style: labelStyle, children: option.label }),
|
|
1277
|
+
option.description && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native_paper11.Text, { variant: "labelSmall", style: descriptionStyle, children: option.description })
|
|
1288
1278
|
] })
|
|
1289
1279
|
] }) }, option.value);
|
|
1290
1280
|
}) });
|
|
@@ -1298,7 +1288,7 @@ function MultiSection({
|
|
|
1298
1288
|
allClearLabels
|
|
1299
1289
|
}) {
|
|
1300
1290
|
const allSelected = section.value.length === section.options.length;
|
|
1301
|
-
return /* @__PURE__ */ (0,
|
|
1291
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
|
|
1302
1292
|
section.options.map((option, i) => {
|
|
1303
1293
|
const selected = section.value.includes(option.value);
|
|
1304
1294
|
const rowColor = selected ? onAccentColor : mutedColor;
|
|
@@ -1306,12 +1296,12 @@ function MultiSection({
|
|
|
1306
1296
|
const nextSelected = selected && i < section.options.length - 1 && section.value.includes(section.options[i + 1].value);
|
|
1307
1297
|
const itemStyle = [styles15.item, selected && { backgroundColor: accentColor }, prevSelected && { borderTopLeftRadius: 0, borderTopRightRadius: 0 }, nextSelected && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }];
|
|
1308
1298
|
const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
|
|
1309
|
-
return /* @__PURE__ */ (0,
|
|
1310
|
-
option.icon && /* @__PURE__ */ (0,
|
|
1311
|
-
/* @__PURE__ */ (0,
|
|
1299
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_feedback_press4.TouchableRipple, { onPress: () => section.onChange(selected ? section.value.filter((v) => v !== option.value) : [...section.value, option.value]), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react_native19.View, { style: styles15.itemRow, children: [
|
|
1300
|
+
option.icon && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native_paper11.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
|
|
1301
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native_paper11.Text, { variant: "labelLarge", style: [styles15.itemLabelFlex, labelStyle], children: option.label })
|
|
1312
1302
|
] }) }, option.value);
|
|
1313
1303
|
}),
|
|
1314
|
-
section.allClear && /* @__PURE__ */ (0,
|
|
1304
|
+
section.allClear && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_feedback_press4.TouchableRipple, { onPress: () => section.onChange(allSelected ? [] : section.options.map((o) => o.value)), style: styles15.item, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native_paper11.Text, { variant: "labelLarge", style: [styles15.allClearLabel, { fontFamily: labelFontFamily, color: accentColor }], children: allSelected ? allClearLabels.clear : allClearLabels.all }) })
|
|
1315
1305
|
] });
|
|
1316
1306
|
}
|
|
1317
1307
|
var styles15 = import_react_native19.StyleSheet.create({
|
|
@@ -1384,21 +1374,21 @@ var styles15 = import_react_native19.StyleSheet.create({
|
|
|
1384
1374
|
});
|
|
1385
1375
|
|
|
1386
1376
|
// src/ControlSchemePicker.tsx
|
|
1387
|
-
var
|
|
1377
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
1388
1378
|
function ControlSchemePicker({ id, host, value, onChange, options, takenValue, accentColor, mutedColor, onAccentColor, dark, align, alignOverride, rotation, labelFontFamily }) {
|
|
1389
1379
|
const isTouchPrimary = (0, import_core10.useIsTouchPrimaryDevice)();
|
|
1390
1380
|
if (import_react_native20.Platform.OS !== "web" || isTouchPrimary) return null;
|
|
1391
|
-
return /* @__PURE__ */ (0,
|
|
1381
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SectionedDropdown, { id, host, icon: "keyboard-outline", accessibilityLabel: "Control scheme", sections: [{ kind: "single", id: "controls", options, value, onChange, takenValue }], accentColor, mutedColor, onAccentColor, dark, align, alignOverride, rotation, labelFontFamily });
|
|
1392
1382
|
}
|
|
1393
1383
|
|
|
1394
1384
|
// src/CornerActionButtons.tsx
|
|
1395
1385
|
var import_feedback_press5 = require("@rific/feedback-press");
|
|
1396
1386
|
var import_react_native21 = require("react-native");
|
|
1397
|
-
var
|
|
1387
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
1398
1388
|
function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon = "arrow-left", backLabel = "Back", hitSlop }) {
|
|
1399
|
-
return /* @__PURE__ */ (0,
|
|
1400
|
-
/* @__PURE__ */ (0,
|
|
1401
|
-
/* @__PURE__ */ (0,
|
|
1389
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
|
|
1390
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_feedback_press5.IconButton, { icon: backIcon, iconColor: fg, size: 24, style: [styles16.back, { top: 8 + insets.top, left: 8 + insets.left }], hitSlop, onPress: onBack, accessibilityLabel: backLabel }),
|
|
1391
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_feedback_press5.IconButton, { icon: "cog", iconColor: fg, size: 24, style: [styles16.topRight, { top: 8 + insets.top, right: 8 + insets.right }], hitSlop, onPress: onSettings, accessibilityLabel: "Settings" })
|
|
1402
1392
|
] });
|
|
1403
1393
|
}
|
|
1404
1394
|
var styles16 = import_react_native21.StyleSheet.create({
|
|
@@ -1413,17 +1403,17 @@ var styles16 = import_react_native21.StyleSheet.create({
|
|
|
1413
1403
|
// src/CornerStatusBadges.tsx
|
|
1414
1404
|
var import_react_native22 = require("react-native");
|
|
1415
1405
|
var import_react_native_paper12 = require("react-native-paper");
|
|
1416
|
-
var
|
|
1406
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
1417
1407
|
var BADGE_SIZE = 30;
|
|
1418
1408
|
function StatusBadge({ icon }) {
|
|
1419
|
-
return /* @__PURE__ */ (0,
|
|
1409
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native22.View, { style: styles17.badge, children: icon && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native_paper12.Icon, { source: icon, size: 16, color: "rgba(255,255,255,0.85)" }) });
|
|
1420
1410
|
}
|
|
1421
1411
|
function CornerStatusBadges({ orientationMode, p1OnRight, p1, p2 }) {
|
|
1422
1412
|
const p1OnRightSide = orientationMode === "sideBySide" && p1OnRight;
|
|
1423
1413
|
const p2OnLeftSide = orientationMode === "sideBySide" && !p1OnRight;
|
|
1424
|
-
return /* @__PURE__ */ (0,
|
|
1425
|
-
p1 && /* @__PURE__ */ (0,
|
|
1426
|
-
p2 && /* @__PURE__ */ (0,
|
|
1414
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
|
|
1415
|
+
p1 && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native22.View, { pointerEvents: "none", style: p1OnRightSide ? styles17.bottomRight : styles17.bottomLeft, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(StatusBadge, { icon: p1.icon }) }),
|
|
1416
|
+
p2 && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native22.View, { pointerEvents: "none", style: p2OnLeftSide ? styles17.topLeft : styles17.topRight, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(StatusBadge, { icon: p2.icon }) })
|
|
1427
1417
|
] });
|
|
1428
1418
|
}
|
|
1429
1419
|
var styles17 = import_react_native22.StyleSheet.create({
|
|
@@ -1450,7 +1440,7 @@ var import_feedback_press6 = require("@rific/feedback-press");
|
|
|
1450
1440
|
var import_core11 = require("@tastic/core");
|
|
1451
1441
|
var import_react_native23 = require("react-native");
|
|
1452
1442
|
var import_react_native_paper13 = require("react-native-paper");
|
|
1453
|
-
var
|
|
1443
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
1454
1444
|
var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
|
|
1455
1445
|
var DEFAULT_SIZE = 48;
|
|
1456
1446
|
var SWATCH_SIZE = 28;
|
|
@@ -1486,14 +1476,14 @@ function InlineColorPicker({ id, host, value, onChange, previewValue, swatches =
|
|
|
1486
1476
|
const displayValue = displayFor(value);
|
|
1487
1477
|
const contrastColor = (0, import_auto_paper10.getContrastColor)(displayValue);
|
|
1488
1478
|
const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : 0.4);
|
|
1489
|
-
return /* @__PURE__ */ (0,
|
|
1490
|
-
/* @__PURE__ */ (0,
|
|
1491
|
-
/* @__PURE__ */ (0,
|
|
1479
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_react_native23.View, { style: [styles18.anchor, open && styles18.anchorOpen], children: [
|
|
1480
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_feedback_press6.TouchableRipple, { onPress: () => host.toggle(id), borderless: true, style: [styles18.trigger, { backgroundColor: displayValue, borderRadius: size / 2, height: size, width: size }], children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native23.View, { ref: triggerRef, collapsable: false, style: styles18.triggerMeasure, children: tag ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native_paper13.Text, { style: [styles18.tagLabel, { color: contrastColor, fontFamily: labelFontFamily, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native_paper13.Icon, { source: icon, size: size * 0.6, color: contrastColor }) }) }),
|
|
1481
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: displayValue, caretBorderWidth: SWATCHES_BORDER_WIDTH, triggerSize: size, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native23.ScrollView, { style: [styles18.swatches, { backgroundColor: menuBg, borderColor: displayValue, width: swatchesWidth, maxHeight }], contentContainerStyle: styles18.swatchesContent, showsVerticalScrollIndicator: false, children: swatches.map((swatch) => {
|
|
1492
1482
|
const selected = swatch.value.toLowerCase() === value.toLowerCase();
|
|
1493
1483
|
const taken = !selected && !!takenValue && swatch.value.toLowerCase() === takenValue.toLowerCase();
|
|
1494
1484
|
const swappable = taken && allowSwapTaken;
|
|
1495
1485
|
const swatchDisplay = displayFor(swatch.value);
|
|
1496
|
-
return /* @__PURE__ */ (0,
|
|
1486
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
1497
1487
|
import_feedback_press6.TouchableRipple,
|
|
1498
1488
|
{
|
|
1499
1489
|
disabled: taken && !swappable,
|
|
@@ -1503,7 +1493,7 @@ function InlineColorPicker({ id, host, value, onChange, previewValue, swatches =
|
|
|
1503
1493
|
},
|
|
1504
1494
|
borderless: true,
|
|
1505
1495
|
style: [styles18.swatch, { backgroundColor: swatchDisplay }, selected && styles18.swatchSelected, taken && !swappable && styles18.swatchTaken],
|
|
1506
|
-
children: selected ? /* @__PURE__ */ (0,
|
|
1496
|
+
children: selected ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native_paper13.Icon, { source: "check", size: 16, color: (0, import_auto_paper10.getContrastColor)(swatchDisplay) }) : swappable ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native_paper13.Icon, { source: "swap-horizontal", size: 16, color: (0, import_auto_paper10.getContrastColor)(swatchDisplay) }) : taken ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native_paper13.Icon, { source: "close", size: 16, color: (0, import_auto_paper10.getContrastColor)(swatchDisplay) }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native23.View, {})
|
|
1507
1497
|
},
|
|
1508
1498
|
swatch.value
|
|
1509
1499
|
);
|
|
@@ -1586,7 +1576,7 @@ var import_auto_paper11 = require("@rific/auto-paper");
|
|
|
1586
1576
|
var import_feedback_press7 = require("@rific/feedback-press");
|
|
1587
1577
|
var import_react_native24 = require("react-native");
|
|
1588
1578
|
var import_react_native_paper14 = require("react-native-paper");
|
|
1589
|
-
var
|
|
1579
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
1590
1580
|
var TRIGGER_HEIGHT = 28;
|
|
1591
1581
|
var POPOVER_WIDTH = 180;
|
|
1592
1582
|
var ROW_HEIGHT = 36;
|
|
@@ -1609,17 +1599,17 @@ function LabeledDropdown({ id, host, options, value, onChange, color, dark, alig
|
|
|
1609
1599
|
onChange(option.value);
|
|
1610
1600
|
host.close();
|
|
1611
1601
|
};
|
|
1612
|
-
return /* @__PURE__ */ (0,
|
|
1613
|
-
/* @__PURE__ */ (0,
|
|
1614
|
-
/* @__PURE__ */ (0,
|
|
1615
|
-
/* @__PURE__ */ (0,
|
|
1602
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_react_native24.View, { style: [styles19.anchor, open && styles19.anchorOpen], children: [
|
|
1603
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_feedback_press7.TouchableRipple, { onPress: () => host.toggle(id), style: styles19.trigger, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_react_native24.View, { ref: triggerRef, collapsable: false, style: styles19.triggerInner, children: [
|
|
1604
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native_paper14.Text, { style: [styles19.triggerLabel, { color }], numberOfLines: 1, children: (selected?.label ?? "").toUpperCase() }),
|
|
1605
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native_paper14.Icon, { source: "menu-down", size: 16, color })
|
|
1616
1606
|
] }) }),
|
|
1617
|
-
/* @__PURE__ */ (0,
|
|
1607
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: 2, triggerSize: TRIGGER_HEIGHT, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native24.ScrollView, { style: [styles19.menu, { backgroundColor: menuBg, borderColor: color, width: POPOVER_WIDTH, maxHeight }], contentContainerStyle: styles19.menuContent, showsVerticalScrollIndicator: false, children: options.map((option) => {
|
|
1618
1608
|
const isSelected = option.value === value;
|
|
1619
1609
|
const onColor = (0, import_auto_paper11.getContrastColor)(color);
|
|
1620
|
-
return /* @__PURE__ */ (0,
|
|
1621
|
-
option.icon && /* @__PURE__ */ (0,
|
|
1622
|
-
/* @__PURE__ */ (0,
|
|
1610
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_feedback_press7.TouchableRipple, { onPress: () => handleSelect(option), style: [styles19.row, isSelected && { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_react_native24.View, { style: styles19.rowInner, children: [
|
|
1611
|
+
option.icon && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native_paper14.Icon, { source: option.icon, size: 18, color: isSelected ? onColor : fg }),
|
|
1612
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native_paper14.Text, { style: [styles19.rowLabel, { color: isSelected ? onColor : fg }], numberOfLines: 1, children: option.label })
|
|
1623
1613
|
] }) }, option.value);
|
|
1624
1614
|
}) }) })
|
|
1625
1615
|
] });
|
|
@@ -1676,9 +1666,9 @@ var styles19 = import_react_native24.StyleSheet.create({
|
|
|
1676
1666
|
}
|
|
1677
1667
|
});
|
|
1678
1668
|
|
|
1679
|
-
// src/
|
|
1680
|
-
function
|
|
1681
|
-
return
|
|
1669
|
+
// src/locateSkiaWebFile.ts
|
|
1670
|
+
function locateSkiaWebFile(file) {
|
|
1671
|
+
return `${process.env.EXPO_BASE_URL ?? ""}/${file}`;
|
|
1682
1672
|
}
|
|
1683
1673
|
|
|
1684
1674
|
// src/PlayerSetupPanel.tsx
|
|
@@ -1690,12 +1680,12 @@ var import_react_native_paper16 = require("react-native-paper");
|
|
|
1690
1680
|
var import_feedback_press8 = require("@rific/feedback-press");
|
|
1691
1681
|
var import_react_native25 = require("react-native");
|
|
1692
1682
|
var import_react_native_paper15 = require("react-native-paper");
|
|
1693
|
-
var
|
|
1683
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
1694
1684
|
function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily = MONO_FONT }) {
|
|
1695
1685
|
const readyButtonStyle = [styles20.readyButton, { borderColor: color }, ready && { backgroundColor: color }, style];
|
|
1696
1686
|
const labelTextStyle = { fontFamily: labelFontFamily, fontWeight: "bold" };
|
|
1697
1687
|
const labelColorStyle = { color: ready ? "#000000" : color };
|
|
1698
|
-
return /* @__PURE__ */ (0,
|
|
1688
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_feedback_press8.TouchableRipple, { onPress: onToggleReady, borderless: true, style: readyButtonStyle, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_native_paper15.Text, { variant: "labelLarge", style: [labelTextStyle, labelColorStyle], children: ready ? "READY \u2713" : "READY?" }) });
|
|
1699
1689
|
}
|
|
1700
1690
|
var styles20 = import_react_native25.StyleSheet.create({
|
|
1701
1691
|
readyButton: {
|
|
@@ -1760,7 +1750,7 @@ function useZoneClampedAlign(open, contentWidth, contentHeight, rotation = 0) {
|
|
|
1760
1750
|
}
|
|
1761
1751
|
|
|
1762
1752
|
// src/PlayerSetupPanel.tsx
|
|
1763
|
-
var
|
|
1753
|
+
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
1764
1754
|
function getColorPopoverId(idPrefix) {
|
|
1765
1755
|
return `${idPrefix}-color`;
|
|
1766
1756
|
}
|
|
@@ -1783,13 +1773,13 @@ function PlayerSetupPanel({ idPrefix, host, label, labelFontFamily, color, onCol
|
|
|
1783
1773
|
const seatPickerId = showProfilePicker ? getProfilePopoverId(idPrefix) : getCpuDifficultyPopoverId(idPrefix);
|
|
1784
1774
|
const seatPickerSize = showProfilePicker ? (0, import_profile2.getProfilePickerContentSize)(profiles.length, !!onManageProfiles, colorSection) : (0, import_profile2.getOptionPickerContentSize)(cpuDifficultyOptions?.length ?? 0, colorSection);
|
|
1785
1775
|
const seatPickerAlign = useZoneClampedAlign(seatButton && popover.openId === seatPickerId, seatPickerSize.width, seatPickerSize.height);
|
|
1786
|
-
return /* @__PURE__ */ (0,
|
|
1787
|
-
showProfilePicker ? /* @__PURE__ */ (0,
|
|
1788
|
-
(!seatButton || secondPicker) && /* @__PURE__ */ (0,
|
|
1789
|
-
!seatButton && /* @__PURE__ */ (0,
|
|
1776
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_react_native27.View, { style: [styles21.panel, ownPopoverOpen && styles21.panelOpen], children: [
|
|
1777
|
+
showProfilePicker ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_profile2.ProfilePicker, { idPrefix, host: popover, profiles, selectedId: selectedProfileId ?? null, takenId: takenProfileId, color, dark, guestLabel: guestLabel ?? "GUEST", onSelect: (profile) => onProfileSelect?.(profile), onManage: onManageProfiles, colorSection, alignOverride: seatPickerAlign }) : showCpuDifficulty ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_profile2.OptionPicker, { id: getCpuDifficultyPopoverId(idPrefix), host: popover, options: cpuDifficultyOptions, value: cpuDifficulty, onChange: onCpuDifficultyChange, color, dark, icon: cpuIcon, colorSection, alignOverride: seatPickerAlign }) : label && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react_native_paper16.Text, { variant: "labelSmall", style: { color, fontFamily: labelFontFamily }, children: label }),
|
|
1778
|
+
(!seatButton || secondPicker) && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_react_native27.View, { style: [styles21.pickerRow, pickerRowPopoverOpen && styles21.pickerRowOpen], children: [
|
|
1779
|
+
!seatButton && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(InlineColorPicker, { id: getColorPopoverId(idPrefix), host: popover, value: color, onChange: onColorChange, swatches, takenValue: takenColor, allowSwapTaken, alignOverride: colorAlignOverride, rotation: colorRotation, dark, tag: isHuman ? selectedProfile?.tag : void 0, icon: isHuman ? humanIcon : cpuIcon }),
|
|
1790
1780
|
secondPicker
|
|
1791
1781
|
] }),
|
|
1792
|
-
isHuman && showReadyButton && onToggleReady && /* @__PURE__ */ (0,
|
|
1782
|
+
isHuman && showReadyButton && onToggleReady && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ReadyButton, { color, ready: ready ?? false, onToggleReady })
|
|
1793
1783
|
] });
|
|
1794
1784
|
}
|
|
1795
1785
|
var styles21 = import_react_native27.StyleSheet.create({
|
|
@@ -1825,23 +1815,23 @@ var styles21 = import_react_native27.StyleSheet.create({
|
|
|
1825
1815
|
|
|
1826
1816
|
// src/PressAwayOverlay.tsx
|
|
1827
1817
|
var import_react_native28 = require("react-native");
|
|
1828
|
-
var
|
|
1818
|
+
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
1829
1819
|
function PressAwayOverlay({ active, onPress, style }) {
|
|
1830
1820
|
if (!active) return null;
|
|
1831
|
-
return /* @__PURE__ */ (0,
|
|
1821
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_native28.Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [import_react_native28.StyleSheet.absoluteFill, style] });
|
|
1832
1822
|
}
|
|
1833
1823
|
|
|
1834
1824
|
// src/SharedActionBand.tsx
|
|
1835
1825
|
var import_feedback_press9 = require("@rific/feedback-press");
|
|
1836
1826
|
var import_react_native29 = require("react-native");
|
|
1837
|
-
var
|
|
1827
|
+
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
1838
1828
|
function SharedActionBand({ onBack, onSettings, onRandomize, onReset, fg, popoverOpen, children }) {
|
|
1839
|
-
return /* @__PURE__ */ (0,
|
|
1840
|
-
/* @__PURE__ */ (0,
|
|
1841
|
-
/* @__PURE__ */ (0,
|
|
1842
|
-
onRandomize && /* @__PURE__ */ (0,
|
|
1843
|
-
onReset && /* @__PURE__ */ (0,
|
|
1844
|
-
/* @__PURE__ */ (0,
|
|
1829
|
+
return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_react_native29.View, { style: [styles22.column, popoverOpen && styles22.columnOpen], children: [
|
|
1830
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_react_native29.View, { style: styles22.actionsRow, children: [
|
|
1831
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_feedback_press9.IconButton, { icon: "arrow-left", iconColor: fg, size: 20, onPress: onBack, accessibilityLabel: "Back" }),
|
|
1832
|
+
onRandomize && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_feedback_press9.IconButton, { icon: "dice-multiple", iconColor: fg, size: 18, onPress: onRandomize, accessibilityLabel: "Randomize match settings" }),
|
|
1833
|
+
onReset && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_feedback_press9.IconButton, { icon: "restore", iconColor: fg, size: 18, onPress: onReset, accessibilityLabel: "Reset match settings to defaults" }),
|
|
1834
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_feedback_press9.IconButton, { icon: "cog", iconColor: fg, size: 20, onPress: onSettings, accessibilityLabel: "Settings" })
|
|
1845
1835
|
] }),
|
|
1846
1836
|
children
|
|
1847
1837
|
] });
|
|
@@ -1865,7 +1855,7 @@ var styles22 = import_react_native29.StyleSheet.create({
|
|
|
1865
1855
|
var import_react13 = require("react");
|
|
1866
1856
|
var import_react_native30 = require("react-native");
|
|
1867
1857
|
var import_react_native_reanimated2 = __toESM(require("react-native-reanimated"));
|
|
1868
|
-
var
|
|
1858
|
+
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
1869
1859
|
var STAGGER_MS = 45;
|
|
1870
1860
|
var LETTER_DURATION_MS = 320;
|
|
1871
1861
|
function getStaggeredWordDuration(letterCount) {
|
|
@@ -1881,10 +1871,10 @@ function AnimatedLetter({ char, index, color, reducedMotion, fontFamily, fontSiz
|
|
|
1881
1871
|
opacity: progress.value,
|
|
1882
1872
|
transform: [{ translateY: (1 - progress.value) * 12 }, { scale: 0.85 + progress.value * 0.15 }]
|
|
1883
1873
|
}));
|
|
1884
|
-
return /* @__PURE__ */ (0,
|
|
1874
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_native_reanimated2.default.Text, { allowFontScaling: false, style: [{ color, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }, style], children: char });
|
|
1885
1875
|
}
|
|
1886
1876
|
function StaggeredWord({ word, color, fontFamily, fontSize, lineHeight, reducedMotion, startIndex = 0, fontWeight = "bold", letterSpacing, style, onLayout }) {
|
|
1887
|
-
return /* @__PURE__ */ (0,
|
|
1877
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_native30.View, { style: [styles23.row, style], onLayout, children: word.split("").map((char, i) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(AnimatedLetter, { char, index: startIndex + i, color, reducedMotion, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }, i)) });
|
|
1888
1878
|
}
|
|
1889
1879
|
var styles23 = import_react_native30.StyleSheet.create({
|
|
1890
1880
|
row: {
|
|
@@ -1894,12 +1884,12 @@ var styles23 = import_react_native30.StyleSheet.create({
|
|
|
1894
1884
|
|
|
1895
1885
|
// src/UpdateDialog.tsx
|
|
1896
1886
|
var import_updater2 = require("@rific/updater");
|
|
1897
|
-
var
|
|
1887
|
+
var import_jsx_runtime28 = require("react/jsx-runtime");
|
|
1898
1888
|
function UpdateDialog({ rotation = 0, autoCheck = true, autoPrompt = true, onError }) {
|
|
1899
1889
|
const { manifest, onConfirm, respond } = useUpdateConfirmBridge();
|
|
1900
1890
|
(0, import_updater2.useUpdater)({ autoCheck, autoPrompt, onConfirm, onError });
|
|
1901
1891
|
const date = manifest ? new Date(manifest.createdAt) : null;
|
|
1902
|
-
return /* @__PURE__ */ (0,
|
|
1892
|
+
return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ConfirmDialog, { visible: !!manifest, title: "Update Available", message: date ? `Released ${date.toLocaleDateString()} at ${date.toLocaleTimeString()}` : "", confirmLabel: "Restart", cancelLabel: "Later", icon: "update", rotation, onConfirm: () => respond(true), onCancel: () => respond(false) });
|
|
1903
1893
|
}
|
|
1904
1894
|
|
|
1905
1895
|
// src/useQuitConfirmation.ts
|
|
@@ -1950,7 +1940,6 @@ function useQuitConfirmation(hasProgress, onConfirmedBack) {
|
|
|
1950
1940
|
RotatedFrame,
|
|
1951
1941
|
SectionedDropdown,
|
|
1952
1942
|
SharedActionBand,
|
|
1953
|
-
SkiaGate,
|
|
1954
1943
|
StaggeredWord,
|
|
1955
1944
|
StatRow,
|
|
1956
1945
|
StatSection,
|
|
@@ -1965,6 +1954,7 @@ function useQuitConfirmation(hasProgress, onConfirmedBack) {
|
|
|
1965
1954
|
getStaggeredWordDuration,
|
|
1966
1955
|
isCompactDialogHeight,
|
|
1967
1956
|
loadSkiaWeb,
|
|
1957
|
+
locateSkiaWebFile,
|
|
1968
1958
|
overlayActionStyles,
|
|
1969
1959
|
overlayStyles,
|
|
1970
1960
|
useAutoAlign,
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
SkiaGate
|
|
3
|
-
} from "./chunk-XEOFR236.mjs";
|
|
4
1
|
import {
|
|
5
2
|
AchievementCatalogSection,
|
|
6
3
|
AchievementRow,
|
|
@@ -38,6 +35,10 @@ import {
|
|
|
38
35
|
overlayStyles,
|
|
39
36
|
useOverlayCardColors
|
|
40
37
|
} from "./chunk-PUF6TABL.mjs";
|
|
38
|
+
import {
|
|
39
|
+
loadSkiaWeb,
|
|
40
|
+
locateSkiaWebFile
|
|
41
|
+
} from "./chunk-3TCJHMJP.mjs";
|
|
41
42
|
|
|
42
43
|
// src/AchievementUnlockList.tsx
|
|
43
44
|
import { getContrastColor, useAutoPaperTheme } from "@rific/auto-paper";
|
|
@@ -95,10 +96,11 @@ import { ScrollView, StyleSheet as StyleSheet2, View as View2 } from "react-nati
|
|
|
95
96
|
import { Icon as Icon2, Text as Text2 } from "react-native-paper";
|
|
96
97
|
|
|
97
98
|
// src/TriggerGaugeHost.tsx
|
|
99
|
+
import { lazy, Suspense } from "react";
|
|
98
100
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
99
|
-
var
|
|
101
|
+
var LazyTriggerGauge = lazy(() => loadSkiaWeb().then(() => import("./TriggerGauge-GIHKGAZE.mjs").then((mod) => ({ default: mod.TriggerGauge }))));
|
|
100
102
|
function TriggerGaugeHost(props) {
|
|
101
|
-
return /* @__PURE__ */ jsx2(
|
|
103
|
+
return /* @__PURE__ */ jsx2(Suspense, { fallback: null, children: /* @__PURE__ */ jsx2(LazyTriggerGauge, { ...props }) });
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
// src/SectionedDropdown.tsx
|
|
@@ -434,11 +436,6 @@ var styles4 = StyleSheet4.create({
|
|
|
434
436
|
}
|
|
435
437
|
});
|
|
436
438
|
|
|
437
|
-
// src/loadSkiaWeb.ts
|
|
438
|
-
function loadSkiaWeb() {
|
|
439
|
-
return Promise.resolve();
|
|
440
|
-
}
|
|
441
|
-
|
|
442
439
|
// src/PlayerSetupPanel.tsx
|
|
443
440
|
import { getOptionPickerContentSize, getProfilePickerContentSize, OptionPicker, ProfilePicker } from "@tastic/profile";
|
|
444
441
|
import { StyleSheet as StyleSheet6, View as View5 } from "react-native";
|
|
@@ -647,7 +644,6 @@ export {
|
|
|
647
644
|
RotatedFrame,
|
|
648
645
|
SectionedDropdown,
|
|
649
646
|
SharedActionBand,
|
|
650
|
-
SkiaGate,
|
|
651
647
|
StaggeredWord,
|
|
652
648
|
StatRow,
|
|
653
649
|
StatSection,
|
|
@@ -662,6 +658,7 @@ export {
|
|
|
662
658
|
getStaggeredWordDuration,
|
|
663
659
|
isCompactDialogHeight,
|
|
664
660
|
loadSkiaWeb,
|
|
661
|
+
locateSkiaWebFile,
|
|
665
662
|
overlayActionStyles,
|
|
666
663
|
overlayStyles,
|
|
667
664
|
useAutoAlign,
|
|
@@ -17,29 +17,25 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
17
|
};
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
|
|
20
|
-
// src/
|
|
21
|
-
var
|
|
22
|
-
__export(
|
|
23
|
-
|
|
20
|
+
// src/skia/index.ts
|
|
21
|
+
var skia_exports = {};
|
|
22
|
+
__export(skia_exports, {
|
|
23
|
+
loadSkiaWeb: () => loadSkiaWeb,
|
|
24
|
+
locateSkiaWebFile: () => locateSkiaWebFile
|
|
24
25
|
});
|
|
25
|
-
module.exports = __toCommonJS(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
function
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return () => {
|
|
36
|
-
cancelled = true;
|
|
37
|
-
};
|
|
38
|
-
}, [getComponent]);
|
|
39
|
-
if (!Component) return fallback;
|
|
40
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Component, { ...componentProps });
|
|
26
|
+
module.exports = __toCommonJS(skia_exports);
|
|
27
|
+
|
|
28
|
+
// src/loadSkiaWeb.ts
|
|
29
|
+
function loadSkiaWeb() {
|
|
30
|
+
return Promise.resolve();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/locateSkiaWebFile.ts
|
|
34
|
+
function locateSkiaWebFile(file) {
|
|
35
|
+
return `${process.env.EXPO_BASE_URL ?? ""}/${file}`;
|
|
41
36
|
}
|
|
42
37
|
// Annotate the CommonJS export names for ESM import in node:
|
|
43
38
|
0 && (module.exports = {
|
|
44
|
-
|
|
39
|
+
loadSkiaWeb,
|
|
40
|
+
locateSkiaWebFile
|
|
45
41
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tastic/hud",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Visual component kit for local-multiplayer React Native games: inline (non-modal) popovers, dropdowns, color pickers, gauges, ready buttons, and dialogs, built to stay scoped to one player's own zone on a shared screen",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"import": "./dist/guide/index.mjs",
|
|
49
49
|
"require": "./dist/guide/index.js"
|
|
50
50
|
},
|
|
51
|
-
"./skia
|
|
52
|
-
"react-native": "./src/
|
|
53
|
-
"browser": "./src/
|
|
54
|
-
"types": "./dist/
|
|
55
|
-
"import": "./dist/
|
|
56
|
-
"require": "./dist/
|
|
51
|
+
"./skia": {
|
|
52
|
+
"react-native": "./src/skia/index.ts",
|
|
53
|
+
"browser": "./src/skia/index.ts",
|
|
54
|
+
"types": "./dist/skia/index.d.ts",
|
|
55
|
+
"import": "./dist/skia/index.mjs",
|
|
56
|
+
"require": "./dist/skia/index.js"
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"main": "dist/index.js",
|
package/src/TriggerGaugeHost.tsx
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { TriggerGaugeProps } from './TriggerGauge'
|
|
1
|
+
import { lazy, Suspense } from 'react'
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
3
|
+
import { loadSkiaWeb } from './loadSkiaWeb'
|
|
4
|
+
import type { TriggerGaugeProps } from './TriggerGauge'
|
|
5
|
+
|
|
6
|
+
// TriggerGauge.tsx imports @shopify/react-native-skia at its top level, and this file is reached from
|
|
7
|
+
// the main barrel, so a static import here would evaluate Skia the moment any app imports
|
|
8
|
+
// `@tastic/hud`. On web that binds Skia to a CanvasKit that hasn't loaded yet (its `Skia` object is
|
|
9
|
+
// built from global.CanvasKit at module-evaluation time), and in every app's Jest run it throws
|
|
10
|
+
// ("Native Skia Module failed to correctly install JSI Bindings!": no app mocks Skia). So the gauge
|
|
11
|
+
// module is only imported when a gauge mounts, behind loadSkiaWeb(), which loads CanvasKit on web and
|
|
12
|
+
// resolves at once on native. This is the web half of the fleet's Skia host pair (see the README's
|
|
13
|
+
// "Skia on web"), used on both platforms here: an app's own native host file can import its Skia
|
|
14
|
+
// component directly, but a package barrel can't. The lazy component is created once, at module
|
|
15
|
+
// scope, so its identity never changes between renders.
|
|
16
|
+
const LazyTriggerGauge = lazy(() => loadSkiaWeb().then(() => import('./TriggerGauge').then((mod) => ({ default: mod.TriggerGauge }))))
|
|
15
17
|
|
|
16
18
|
export default function TriggerGaugeHost(props: TriggerGaugeProps) {
|
|
17
|
-
return
|
|
19
|
+
return (
|
|
20
|
+
<Suspense fallback={null}>
|
|
21
|
+
<LazyTriggerGauge {...props} />
|
|
22
|
+
</Suspense>
|
|
23
|
+
)
|
|
18
24
|
}
|
package/src/guide/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The how-to-play entry point (`@tastic/hud/guide`) - a standalone build reachable without ever
|
|
2
|
-
// touching the main barrel, the same reason `@tastic/hud/skia
|
|
2
|
+
// touching the main barrel, the same reason `@tastic/hud/skia` is. This file and everything it
|
|
3
3
|
// imports must stay free of @tastic/core, @tastic/profile, @tastic/split-screen, @tastic/achievements,
|
|
4
4
|
// @rific/updater and Skia: those are optional peers of this package (see package.json's
|
|
5
5
|
// peerDependenciesMeta) precisely so an app with none of them can install @tastic/hud and use only
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export { MONO_FONT } from './fonts'
|
|
|
14
14
|
export { getInlineColorPickerContentSize, InlineColorPicker } from './InlineColorPicker'
|
|
15
15
|
export { getLabeledDropdownContentHeight, LABELED_DROPDOWN_POPOVER_WIDTH, LabeledDropdown, type LabeledDropdownOption } from './LabeledDropdown'
|
|
16
16
|
export { loadSkiaWeb } from './loadSkiaWeb'
|
|
17
|
+
export { locateSkiaWebFile } from './locateSkiaWebFile'
|
|
17
18
|
export { overlayActionStyles, overlayStyles } from './overlayCard'
|
|
18
19
|
export { OverlayBackdrop, type OverlayBackdropProps, OverlayCard, type OverlayCardProps, type OverlayCardThemeColors, useOverlayCardColors } from './OverlaySurface'
|
|
19
20
|
export { getColorPopoverId, getCpuDifficultyPopoverId, getProfilePopoverId, PlayerSetupPanel, type PlayerSetupPanelProps } from './PlayerSetupPanel'
|
|
@@ -23,12 +24,11 @@ export { ReadyButton } from './ReadyButton'
|
|
|
23
24
|
export { RotatedFrame, type RotatedFrameProps } from './RotatedFrame'
|
|
24
25
|
export { type MenuOption, type MenuSection, type MultiSelectSection, SectionedDropdown, type SingleSelectSection } from './SectionedDropdown'
|
|
25
26
|
export { SharedActionBand } from './SharedActionBand'
|
|
26
|
-
export { SkiaGate, type SkiaGateProps } from './SkiaGate'
|
|
27
27
|
export { getStaggeredWordDuration, StaggeredWord, type StaggeredWordProps } from './StaggeredWord'
|
|
28
28
|
export { StatRow } from './StatRow'
|
|
29
29
|
export { StatSection } from './StatSection'
|
|
30
30
|
// TriggerGauge itself (the raw Skia-based component) is deliberately NOT exported here, even
|
|
31
|
-
// though a native-only consumer could safely skip TriggerGaugeHost's
|
|
31
|
+
// though a native-only consumer could safely skip TriggerGaugeHost's lazy indirection. A
|
|
32
32
|
// value re-export forces the bundler to fold TriggerGauge.tsx's own `Skia` import into a chunk
|
|
33
33
|
// that this entry point imports eagerly at the top level - since that chunk is then shared with
|
|
34
34
|
// TriggerGaugeHost's own dynamic import too, it defeats TriggerGaugeHost's deferred loading for
|
package/src/loadSkiaWeb.web.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { LoadSkiaWeb } from '@shopify/react-native-skia/src/web'
|
|
2
2
|
|
|
3
|
+
import { locateSkiaWebFile } from './locateSkiaWebFile'
|
|
4
|
+
|
|
3
5
|
// Skia's web target renders through CanvasKit, a WASM build of Skia fetched at runtime - nothing
|
|
4
6
|
// using @shopify/react-native-skia's React components can draw a single frame until this resolves.
|
|
5
|
-
// Kept as a thin wrapper (rather than importing LoadSkiaWeb directly at call sites)
|
|
6
|
-
//
|
|
7
|
+
// Kept as a thin wrapper (rather than importing LoadSkiaWeb directly at call sites) so this file's
|
|
8
|
+
// `.ts` counterpart, loadSkiaWeb.ts, gives native an identically-shaped async no-op - Metro's
|
|
7
9
|
// platform-extension resolution picks whichever one matches the bundle target, so canvaskit-wasm's
|
|
8
|
-
// `require('fs')
|
|
10
|
+
// `require('fs')` never even gets resolved into a native/iOS/Android bundle - and so the wasm is
|
|
11
|
+
// always fetched from the app's base URL (see locateSkiaWebFile.ts).
|
|
9
12
|
export function loadSkiaWeb(): Promise<void> {
|
|
10
|
-
return LoadSkiaWeb()
|
|
13
|
+
return LoadSkiaWeb({ locateFile: locateSkiaWebFile })
|
|
11
14
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// The `locateFile` for CanvasKit's WASM fetch: `<baseUrl>/<file>`, so `/snake/canvaskit.wasm` under
|
|
2
|
+
// an Expo base URL of `/snake` and `/canvaskit.wasm` with none. That is where an Expo web export
|
|
3
|
+
// serves the app's `public/canvaskit.wasm`. Without a `locateFile`, canvaskit-wasm 0.41 resolves the
|
|
4
|
+
// file next to the running script, or next to the page when no script is running: at `/snake` (no
|
|
5
|
+
// trailing slash) that is `/canvaskit.wasm`, and in a static-output app it can land under
|
|
6
|
+
// `/_expo/static/js/web/`. LoadSkiaWeb also keeps its first load's promise, a failed one included,
|
|
7
|
+
// for the life of the page, so whichever call loads CanvasKit first has to pass this.
|
|
8
|
+
//
|
|
9
|
+
// `process.env.EXPO_BASE_URL` is `experiments.baseUrl` with its trailing slashes stripped ('' when
|
|
10
|
+
// unset). babel-preset-expo inlines it as a string literal in every file Metro transforms,
|
|
11
|
+
// node_modules included, so this source picks up the consuming app's value with no wiring. It is
|
|
12
|
+
// read at call time, so a test (where it isn't inlined) can set it. No Skia import, so the barrel and
|
|
13
|
+
// `@tastic/hud/skia` can export it to native too.
|
|
14
|
+
export function locateSkiaWebFile(file: string): string {
|
|
15
|
+
return `${process.env.EXPO_BASE_URL ?? ''}/${file}`
|
|
16
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// The Skia entry point (`@tastic/hud/skia`): the web CanvasKit loader every app's Skia host files use,
|
|
2
|
+
// and the wasm URL rule it applies, reachable without the main barrel. Several Skia apps lack the
|
|
3
|
+
// barrel's heavier peers (@tastic/profile, @tastic/split-screen), and importing `@tastic/hud` there
|
|
4
|
+
// fails to bundle, so this is the one import path every app's host files share.
|
|
5
|
+
//
|
|
6
|
+
// package.json points both "react-native" and "browser" at this file. That is fine because it has no
|
|
7
|
+
// .web twin itself: Metro doesn't platform-resolve an exports-map target, but it does resolve the
|
|
8
|
+
// relative imports below, so web bundles get loadSkiaWeb.web.ts and native ones the no-op. The
|
|
9
|
+
// boundary (only @shopify/react-native-skia's /src/web subpath, and only from the .web file) is
|
|
10
|
+
// enforced by src/__tests__/skiaImports.test.ts.
|
|
11
|
+
export { loadSkiaWeb } from '../loadSkiaWeb'
|
|
12
|
+
export { locateSkiaWebFile } from '../locateSkiaWebFile'
|
package/dist/SkiaGate.d.mts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import * as react from 'react';
|
|
2
|
-
import { ComponentType, ReactNode } from 'react';
|
|
3
|
-
|
|
4
|
-
interface SkiaGateProps<P extends object> {
|
|
5
|
-
getComponent: () => Promise<{
|
|
6
|
-
default: ComponentType<P>;
|
|
7
|
-
}>;
|
|
8
|
-
componentProps: P;
|
|
9
|
-
fallback?: ReactNode;
|
|
10
|
-
}
|
|
11
|
-
declare function SkiaGate<P extends object>({ getComponent, componentProps, fallback }: SkiaGateProps<P>): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | react.JSX.Element | null;
|
|
12
|
-
|
|
13
|
-
export { SkiaGate, type SkiaGateProps };
|
package/dist/SkiaGate.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import * as react from 'react';
|
|
2
|
-
import { ComponentType, ReactNode } from 'react';
|
|
3
|
-
|
|
4
|
-
interface SkiaGateProps<P extends object> {
|
|
5
|
-
getComponent: () => Promise<{
|
|
6
|
-
default: ComponentType<P>;
|
|
7
|
-
}>;
|
|
8
|
-
componentProps: P;
|
|
9
|
-
fallback?: ReactNode;
|
|
10
|
-
}
|
|
11
|
-
declare function SkiaGate<P extends object>({ getComponent, componentProps, fallback }: SkiaGateProps<P>): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | react.JSX.Element | null;
|
|
12
|
-
|
|
13
|
-
export { SkiaGate, type SkiaGateProps };
|
package/dist/SkiaGate.mjs
DELETED
package/dist/chunk-XEOFR236.mjs
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// src/SkiaGate.tsx
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
3
|
-
import { jsx } from "react/jsx-runtime";
|
|
4
|
-
function SkiaGate({ getComponent, componentProps, fallback = null }) {
|
|
5
|
-
const [Component, setComponent] = useState(null);
|
|
6
|
-
useEffect(() => {
|
|
7
|
-
let cancelled = false;
|
|
8
|
-
getComponent().then((mod) => {
|
|
9
|
-
if (!cancelled) setComponent(() => mod.default);
|
|
10
|
-
});
|
|
11
|
-
return () => {
|
|
12
|
-
cancelled = true;
|
|
13
|
-
};
|
|
14
|
-
}, [getComponent]);
|
|
15
|
-
if (!Component) return fallback;
|
|
16
|
-
return /* @__PURE__ */ jsx(Component, { ...componentProps });
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export {
|
|
20
|
-
SkiaGate
|
|
21
|
-
};
|
package/src/SkiaGate.tsx
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { ComponentType, ReactNode } from 'react'
|
|
2
|
-
import { useEffect, useState } from 'react'
|
|
3
|
-
|
|
4
|
-
export interface SkiaGateProps<P extends object> {
|
|
5
|
-
// A dynamic import, e.g. `() => import('./Board')` - hoist to module scope at the call site
|
|
6
|
-
// (never an inline closure written directly in JSX) so its identity stays stable across
|
|
7
|
-
// renders; it's this component's own useEffect dependency below.
|
|
8
|
-
getComponent: () => Promise<{ default: ComponentType<P> }>
|
|
9
|
-
componentProps: P
|
|
10
|
-
// Rendered while getComponent()'s promise is still pending. Defaults to null.
|
|
11
|
-
fallback?: ReactNode
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Native Skia renders through JSI - no WASM/CanvasKit fetch to wait on - so this file just
|
|
15
|
-
// lazily mounts whatever getComponent() resolves to, once it resolves. The .web.tsx sibling
|
|
16
|
-
// (picked by Metro/webpack's own platform-extension resolution for web bundles) is a different
|
|
17
|
-
// implementation entirely: a direct re-export of @shopify/react-native-skia's own WithSkiaWeb,
|
|
18
|
-
// which gates the same mount behind LoadSkiaWeb() first - CanvasKit's WASM build has to actually
|
|
19
|
-
// finish loading before anything that imports '@shopify/react-native-skia' can evaluate its own
|
|
20
|
-
// top-level `Skia` binding without crashing (see loadSkiaWeb.web.ts's own comment).
|
|
21
|
-
//
|
|
22
|
-
// No React.lazy()/Suspense here, deliberately: lazy() can't be called inline during render
|
|
23
|
-
// (react-hooks/static-components - a getComponent prop that isn't referentially stable would
|
|
24
|
-
// give a new component identity, and therefore an unwanted remount, on every render that passes
|
|
25
|
-
// a fresh one) and can't be cached in a ref either (react-hooks/refs forbids reading or writing
|
|
26
|
-
// ref.current during render, only inside an effect). Awaiting the import directly and setting
|
|
27
|
-
// state from the resolved module sidesteps both - setState only ever runs from the promise
|
|
28
|
-
// callback, never synchronously in render or in the effect body itself.
|
|
29
|
-
export function SkiaGate<P extends object>({ getComponent, componentProps, fallback = null }: SkiaGateProps<P>) {
|
|
30
|
-
const [Component, setComponent] = useState<ComponentType<P> | null>(null)
|
|
31
|
-
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
let cancelled = false
|
|
34
|
-
getComponent().then((mod) => {
|
|
35
|
-
if (!cancelled) setComponent(() => mod.default)
|
|
36
|
-
})
|
|
37
|
-
return () => {
|
|
38
|
-
cancelled = true
|
|
39
|
-
}
|
|
40
|
-
}, [getComponent])
|
|
41
|
-
|
|
42
|
-
if (!Component) return fallback
|
|
43
|
-
|
|
44
|
-
return <Component {...componentProps} />
|
|
45
|
-
}
|
package/src/SkiaGate.web.tsx
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// Web needs CanvasKit's WASM loaded before any Skia <Canvas> evaluates - see loadSkiaWeb.web.ts's
|
|
2
|
-
// own comment for why a plain useEffect-based loader can't fix this (it's a module-evaluation-
|
|
3
|
-
// order problem, not a component-lifecycle one). Only this file (picked by Metro/webpack's
|
|
4
|
-
// platform-extension resolution for web bundles) may import from
|
|
5
|
-
// '@shopify/react-native-skia/src/web' - a static import of that subpath from a native-reachable
|
|
6
|
-
// file pulls canvaskit-wasm's `require('fs')` into the native bundle and breaks iOS/Android
|
|
7
|
-
// builds; this file vs. SkiaGate.tsx is what actually keeps it out, the same split
|
|
8
|
-
// loadSkiaWeb.web.ts/loadSkiaWeb.ts already use.
|
|
9
|
-
export { WithSkiaWeb as SkiaGate } from '@shopify/react-native-skia/src/web'
|