@tastic/hud 0.22.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 +85 -15
- package/dist/chrome/index.d.mts +87 -18
- package/dist/chrome/index.d.ts +87 -18
- package/dist/chrome/index.js +309 -22
- package/dist/chrome/index.mjs +13 -1
- package/dist/chunk-3TCJHMJP.mjs +14 -0
- package/dist/{chunk-N7A7W72S.mjs → chunk-BXG44LIA.mjs} +298 -19
- package/dist/index.d.mts +13 -84
- package/dist/index.d.ts +13 -84
- package/dist/index.js +120 -127
- package/dist/index.mjs +107 -374
- 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 +9 -8
- package/src/BaseSettingsDialog.tsx +13 -13
- package/src/TriggerGaugeHost.tsx +20 -14
- package/src/chrome/index.ts +12 -4
- 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
|
@@ -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",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"@infinitetoken/jest-config": "^0.5.2",
|
|
89
89
|
"@infinitetoken/tsconfig": "^0.6.0",
|
|
90
90
|
"@rific/auto-paper": "^0.13.3",
|
|
91
|
-
"@rific/core": "^0.
|
|
91
|
+
"@rific/core": "^0.4.0",
|
|
92
92
|
"@rific/feedback-press": "^0.13.0",
|
|
93
93
|
"@rific/updater": "^0.7.1",
|
|
94
94
|
"@shopify/react-native-skia": "^2.14.0",
|
|
@@ -117,6 +117,7 @@
|
|
|
117
117
|
},
|
|
118
118
|
"peerDependencies": {
|
|
119
119
|
"@rific/auto-paper": "^0.13.0",
|
|
120
|
+
"@rific/core": ">=0.4.0",
|
|
120
121
|
"@rific/feedback-press": ">=0.10.2",
|
|
121
122
|
"@rific/updater": ">=0.4.0",
|
|
122
123
|
"@shopify/react-native-skia": ">=2.6.0",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AutoAppearancePicker, Dialog, FadingScrollView, useAutoPaperTheme, useBlurModule, useThemeSettings } from '@rific/auto-paper'
|
|
2
|
+
import { useExpoKeepAwake, useKeepAwakeSettings } from '@rific/core'
|
|
2
3
|
import { Button, SoundContext, TouchableRipple, useHapticSettings, useSoundSettings, useVibration } from '@rific/feedback-press'
|
|
3
4
|
import { useUpdater } from '@rific/updater'
|
|
4
5
|
import { rotateInsets, useIsTouchPrimaryDevice, useRotation, type ViewRotation } from '@tastic/core'
|
|
@@ -56,13 +57,7 @@ export interface BaseSettingsDialogProps {
|
|
|
56
57
|
// @tastic/edge-guard's own swizzle is a no-op on every other platform.
|
|
57
58
|
deferBottomEdgeGestures?: boolean
|
|
58
59
|
onDeferBottomEdgeGestures?: (value: boolean) => void
|
|
59
|
-
//
|
|
60
|
-
// This dialog only shows and reports the toggle; the app itself keeps the screen on while it's set
|
|
61
|
-
// (expo-keep-awake on its match screen), so this package needs no native dependency for it. Shown
|
|
62
|
-
// on the same devices as Lock Orientation (see isTouchPrimary below): a desktop/laptop browser
|
|
63
|
-
// stays awake on its own while someone is playing with its keyboard or mouse.
|
|
64
|
-
keepAwake?: boolean
|
|
65
|
-
onKeepAwakeChange?: (value: boolean) => void
|
|
60
|
+
// No Keep Awake props: that row reads @rific/core's Keep Awake setting itself (see showKeepAwake).
|
|
66
61
|
// Explicit opt-outs for the rows that pull their own state straight from @rific/feedback-press,
|
|
67
62
|
// @rific/auto-paper, and @rific/updater's own shared contexts/hooks rather than from anything the
|
|
68
63
|
// caller passes in - unlike Lock Orientation/Edge Guard above, there's no "value" to omit for
|
|
@@ -97,7 +92,7 @@ export interface BaseSettingsDialogProps {
|
|
|
97
92
|
// is the seam for whatever isn't shared: a game with its own board/difficulty settings (or a stats
|
|
98
93
|
// backup flow, or anything else genuinely specific to it) renders those below Appearance, not by
|
|
99
94
|
// forking this component.
|
|
100
|
-
export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures,
|
|
95
|
+
export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, onShowHowToPlay, children }: BaseSettingsDialogProps) {
|
|
101
96
|
const { colors } = useAutoPaperTheme()
|
|
102
97
|
const ambientRotation = useRotation()
|
|
103
98
|
const rotation = rotationOverride ?? ambientRotation
|
|
@@ -113,7 +108,7 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
|
|
|
113
108
|
// Dialog's own 32pt padding is simply overhung.
|
|
114
109
|
const insets = rotateInsets(useSafeAreaInsets(), rotation)
|
|
115
110
|
const sheetWidth = Math.min(overlayStyles.card.maxWidth, (turned ? window.height : window.width) - 2 * Math.max(insets.left, insets.right, SHEET_GUTTER))
|
|
116
|
-
// Also gates the Lock Orientation and Keep Awake rows below (alongside their own
|
|
111
|
+
// Also gates the Lock Orientation and Keep Awake rows below (alongside their own conditions) - true
|
|
117
112
|
// unconditionally on native (see the hook's own doc), so this only actually excludes a
|
|
118
113
|
// desktop/laptop browser, where a mouse-driven window has no physical orientation to lock
|
|
119
114
|
// against; a resize there would just make the setting silently stop reflowing the layout instead
|
|
@@ -121,6 +116,8 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
|
|
|
121
116
|
const isTouchPrimary = useIsTouchPrimaryDevice()
|
|
122
117
|
const { settings: hapticSettings, set: setHapticSettings } = useHapticSettings()
|
|
123
118
|
const { settings: soundSettings, set: setSoundSettings } = useSoundSettings()
|
|
119
|
+
const { settings: keepAwakeSettings, set: setKeepAwakeSettings } = useKeepAwakeSettings()
|
|
120
|
+
const expoKeepAwake = useExpoKeepAwake()
|
|
124
121
|
// Sound/Haptics toggle themselves: the ripple's automatic press feedback fires on onPressIn,
|
|
125
122
|
// before onPress applies the toggle, so it reflects the OLD enabled value - backwards from what
|
|
126
123
|
// a settings toggle should confirm (turning off would click/buzz, turning on would go silent).
|
|
@@ -160,7 +157,10 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
|
|
|
160
157
|
|
|
161
158
|
const showLockOrientation = isTouchPrimary && lockOrientation !== undefined && onLockOrientationChange !== undefined
|
|
162
159
|
const showEdgeGuard = Platform.OS === 'ios' && deferBottomEdgeGestures !== undefined && onDeferBottomEdgeGestures !== undefined
|
|
163
|
-
|
|
160
|
+
// Only in an app that opted in by handing expo-keep-awake to @rific/core's KeepAwakeProvider:
|
|
161
|
+
// without the module the setting would do nothing. On the same devices as Lock Orientation, since a
|
|
162
|
+
// desktop/laptop browser stays awake on its own while someone plays with its keyboard or mouse.
|
|
163
|
+
const showKeepAwake = isTouchPrimary && expoKeepAwake !== undefined
|
|
164
164
|
const showHaptics = Platform.OS !== 'web' && !hideHaptics
|
|
165
165
|
const showSound = !hideSound
|
|
166
166
|
const showAppearance = !hideAppearance
|
|
@@ -243,11 +243,11 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
|
|
|
243
243
|
</TouchableRipple>
|
|
244
244
|
)}
|
|
245
245
|
|
|
246
|
-
{/*
|
|
246
|
+
{/* The app's own useKeepAwake('game') does the keeping awake; this only flips the shared setting. */}
|
|
247
247
|
{showKeepAwake && (
|
|
248
|
-
<TouchableRipple onPress={() =>
|
|
248
|
+
<TouchableRipple onPress={() => setKeepAwakeSettings({ enabled: !keepAwakeSettings.enabled })} style={styles.toggleButton} accessibilityLabel={`Keep awake ${keepAwakeSettings.enabled ? 'on' : 'off'}`}>
|
|
249
249
|
<View style={styles.toggleContent}>
|
|
250
|
-
<SettingIcon source={
|
|
250
|
+
<SettingIcon source={keepAwakeSettings.enabled ? 'eye' : 'eye-closed'} color={keepAwakeSettings.enabled ? colors.tertiary : colors.onSurfaceVariant} containerColor={keepAwakeSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant} />
|
|
251
251
|
<View style={styles.flexShrink}>
|
|
252
252
|
<Text variant='bodyLarge' style={{ color: colors.onSurface }}>
|
|
253
253
|
Keep Awake
|
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/chrome/index.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// The fleet-chrome entry point (`@tastic/hud/chrome`): the shared dialogs, stats screen pieces, corner
|
|
2
|
-
// buttons and style tokens, reachable without the main barrel, for an
|
|
3
|
-
// peers. Hangman is the reason it exists: it has @tastic/core,
|
|
4
|
-
// no Skia, no @tastic/profile and no @tastic/split-screen.
|
|
5
|
-
//
|
|
2
|
+
// buttons, popover primitives, color picker and style tokens, reachable without the main barrel, for an
|
|
3
|
+
// app that has none of its heavy peers. Hangman is the reason it exists: it has @tastic/core,
|
|
4
|
+
// @rific/updater and the Paper stack, but no Skia, no @tastic/profile and no @tastic/split-screen. The
|
|
5
|
+
// popover pieces (PopoverBody, PressAwayOverlay, usePopoverHost, useAutoAlign) and InlineColorPicker are
|
|
6
|
+
// here for @tastic/profile 0.16+, which imports them from this entry instead of the main barrel so its
|
|
7
|
+
// ProfilePicker and ProfilesScreen work in Hangman. Everything reachable from this file must stay free of
|
|
8
|
+
// those three and of the main barrel; @tastic/achievements may appear only as `import type`.
|
|
6
9
|
// AchievementUnlockList is left out on purpose because it value-imports @tastic/profile's ProfileChip.
|
|
7
10
|
// src/__tests__/chromeImports.test.ts walks the real import graph from here and enforces all of it.
|
|
8
11
|
export { AchievementCatalogSection } from '../AchievementCatalogSection'
|
|
@@ -15,12 +18,17 @@ export { ContentGutter } from '../ContentGutter'
|
|
|
15
18
|
export { CornerActionButtons } from '../CornerActionButtons'
|
|
16
19
|
export { COMPACT_DIALOG_HEIGHT, DialogHeader, type DialogHeaderProps, isCompactDialogHeight } from '../DialogHeader'
|
|
17
20
|
export { MONO_FONT } from '../fonts'
|
|
21
|
+
export { getInlineColorPickerContentSize, InlineColorPicker } from '../InlineColorPicker'
|
|
18
22
|
export { overlayActionStyles, overlayStyles } from '../overlayCard'
|
|
19
23
|
export { OverlayBackdrop, type OverlayBackdropProps, OverlayCard, type OverlayCardProps, type OverlayCardThemeColors, useOverlayCardColors } from '../OverlaySurface'
|
|
24
|
+
export { PopoverBody } from '../PopoverBody'
|
|
25
|
+
export { PressAwayOverlay } from '../PressAwayOverlay'
|
|
20
26
|
export { RotatedFrame, type RotatedFrameProps } from '../RotatedFrame'
|
|
21
27
|
export { getStaggeredWordDuration, StaggeredWord, type StaggeredWordProps } from '../StaggeredWord'
|
|
22
28
|
export { StatRow } from '../StatRow'
|
|
23
29
|
export { StatSection } from '../StatSection'
|
|
24
30
|
export { UpdateDialog, type UpdateDialogProps } from '../UpdateDialog'
|
|
31
|
+
export { type AlignResult, type PopoverAlign, type PopoverRotation, type PopoverVerticalAlign, useAutoAlign } from '../useAutoAlign'
|
|
25
32
|
export { useCompactDialog } from '../useCompactDialog'
|
|
33
|
+
export { type PopoverHost, usePopoverHost } from '../usePopoverHost'
|
|
26
34
|
export { useWellColors, WELL_SHADOW, type WellColors } from '../wellSurface'
|
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'
|