@satinhq/kit 0.1.0-beta.3 → 0.1.0-beta.4
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/NOTICE +1 -1
- package/README.md +1 -2
- package/dist/acknowledgements/inventory.json +1 -1
- package/dist/actions/RowActions.d.ts +2 -2
- package/dist/actions/RowActions.js +91 -25
- package/dist/actions/fullSwipeContact.d.ts +73 -0
- package/dist/actions/fullSwipeContact.js +186 -0
- package/dist/actions/rowScrub.d.ts +7 -4
- package/dist/actions/rowScrub.js +39 -37
- package/dist/actions/types.d.ts +13 -7
- package/dist/app/InstallComponents.d.ts +4 -7
- package/dist/app/InstallComponents.js +7 -7
- package/dist/app/InstallationDiagnostics.d.ts +2 -1
- package/dist/app/InstallationDiagnostics.js +2 -1
- package/dist/app/appearance.d.ts +5 -3
- package/dist/app/appearance.js +5 -3
- package/dist/app/appearanceStore.d.ts +3 -2
- package/dist/app/buildInfo.d.ts +4 -4
- package/dist/app/buildInfo.js +2 -2
- package/dist/app/definition.d.ts +11 -3
- package/dist/app/definition.js +2 -0
- package/dist/app/head.d.ts +2 -1
- package/dist/app/head.js +13 -16
- package/dist/app/inlineCSP.d.ts +6 -0
- package/dist/app/inlineCSP.js +25 -0
- package/dist/app/install.d.ts +1 -0
- package/dist/app/install.js +5 -2
- package/dist/app/installCheck.js +32 -86
- package/dist/app/installContext.d.ts +10 -5
- package/dist/app/installContext.js +2 -0
- package/dist/app/installCustom.js +1 -0
- package/dist/app/installExperience.js +0 -60
- package/dist/app/installationFailure.d.ts +3 -2
- package/dist/app/installationFailure.js +1 -1
- package/dist/app/launchScreen.d.ts +6 -2
- package/dist/app/launchScreen.js +104 -1
- package/dist/app/launchScreenRuntime.d.ts +31 -4
- package/dist/app/launchScreenRuntime.js +150 -22
- package/dist/app/launchWatchdog.js +25 -65
- package/dist/app/platformScripts.js +11 -0
- package/dist/app/styles.d.ts +3 -0
- package/dist/app/styles.js +42 -0
- package/dist/app/viewportValue.d.ts +2 -1
- package/dist/app/vite.d.ts +4 -0
- package/dist/app/vite.js +22 -24
- package/dist/content/content.d.ts +35 -30
- package/dist/content/content.js +19 -16
- package/dist/content/layout.d.ts +10 -2
- package/dist/content/layout.js +4 -2
- package/dist/content/scaledMetric.d.ts +2 -1
- package/dist/content/scaledMetric.js +2 -1
- package/dist/controls/ColorPicker.d.ts +2 -4
- package/dist/controls/ColorPicker.js +2 -4
- package/dist/controls/DatePicker.d.ts +4 -4
- package/dist/controls/DatePicker.js +2 -2
- package/dist/controls/ProgressView.d.ts +4 -4
- package/dist/controls/ProgressView.js +1 -2
- package/dist/data/database.d.ts +9 -8
- package/dist/data/database.js +4 -4
- package/dist/docs/reference.md +982 -964
- package/dist/docs/tailwind.md +76 -0
- package/dist/docs/tokens.md +1 -1
- package/dist/gestures/GestureView.d.ts +3 -1
- package/dist/gestures/GestureView.js +2 -1
- package/dist/gestures/gesture.d.ts +22 -2
- package/dist/gestures/gesture.js +7 -2
- package/dist/glass/GlassEffect.d.ts +2 -1
- package/dist/glass/GlassEffect.js +2 -1
- package/dist/glass/GlassPresentation.d.ts +1 -0
- package/dist/glass/GlassPresentation.js +21 -4
- package/dist/glass/TabBar.js +23 -1
- package/dist/glass/errorAlert.js +2 -1
- package/dist/glass/errors.d.ts +3 -1
- package/dist/glass/fallback/runtime.d.ts +1 -0
- package/dist/glass/fallback/runtime.js +8 -1
- package/dist/glass/liquidContext.d.ts +2 -0
- package/dist/glass/menuPresenter.d.ts +2 -0
- package/dist/glass/regions.d.ts +2 -1
- package/dist/glass/renderSpecs.d.ts +1 -0
- package/dist/glass/renderSpecs.js +1 -0
- package/dist/glass/webgpu/controlRim.d.ts +29 -0
- package/dist/glass/webgpu/controlRim.js +68 -0
- package/dist/glass/webgpu/passes.d.ts +2 -2
- package/dist/glass/webgpu/production.d.ts +1 -0
- package/dist/glass/webgpu/production.js +27 -1
- package/dist/glass/webgpu/rasterUniforms.js +9 -0
- package/dist/glass/webgpu/shaders.d.ts +2 -2
- package/dist/glass/webgpu/shaders.js +5 -1
- package/dist/haptics/SensoryButton.d.ts +4 -4
- package/dist/haptics/SensoryButton.js +3 -3
- package/dist/image/Image.d.ts +6 -3
- package/dist/image/Image.js +4 -3
- package/dist/image/asset.d.ts +2 -1
- package/dist/keyboard/adapter.d.ts +3 -4
- package/dist/keyboard/geometry.d.ts +1 -1
- package/dist/list-form/ListForm.d.ts +8 -7
- package/dist/list-form/ListForm.js +2 -2
- package/dist/list-form/editing.d.ts +5 -6
- package/dist/list-form/editing.js +4 -5
- package/dist/list-form/offsets.d.ts +2 -2
- package/dist/list-form/offsets.js +2 -2
- package/dist/media/react.d.ts +2 -1
- package/dist/media/react.js +2 -1
- package/dist/media/types.d.ts +6 -3
- package/dist/motion/TransitionView.d.ts +4 -2
- package/dist/motion/TransitionView.js +3 -2
- package/dist/motion/animationValue.d.ts +1 -1
- package/dist/motion/matchedGeometry.d.ts +6 -5
- package/dist/motion/matchedGeometry.js +2 -1
- package/dist/motion/transition.d.ts +4 -1
- package/dist/motion/transition.js +2 -0
- package/dist/navigation/DetailList.d.ts +1 -1
- package/dist/navigation/DetailList.js +1 -1
- package/dist/navigation/Sidebar.d.ts +3 -1
- package/dist/navigation/Sidebar.js +1 -1
- package/dist/navigation/SidebarSymbol.d.ts +2 -1
- package/dist/navigation/SidebarSymbol.js +2 -1
- package/dist/navigation/navigationContext.d.ts +2 -1
- package/dist/navigation/navigationContext.js +2 -1
- package/dist/navigation/splitContext.d.ts +2 -1
- package/dist/navigation/splitContext.js +2 -1
- package/dist/navigation/toolbarMinimization.d.ts +4 -3
- package/dist/navigation/types.d.ts +7 -5
- package/dist/presentation/Presentation.d.ts +8 -7
- package/dist/presentation/Presentation.js +3 -2
- package/dist/presentation/model.d.ts +2 -1
- package/dist/press/Pressable.d.ts +5 -1
- package/dist/press/Pressable.js +2 -0
- package/dist/press/stateMachine.d.ts +1 -1
- package/dist/public/Label.d.ts +2 -1
- package/dist/public/Label.js +2 -1
- package/dist/public/Screen.d.ts +4 -6
- package/dist/public/actions.d.ts +2 -1
- package/dist/public/actions.js +2 -1
- package/dist/public/buttonBorderShape.d.ts +2 -1
- package/dist/public/colors.d.ts +7 -4
- package/dist/public/colors.js +3 -2
- package/dist/public/controlStyle.d.ts +17 -14
- package/dist/public/controls.d.ts +27 -26
- package/dist/public/controls.js +20 -19
- package/dist/public/deviceActions.d.ts +6 -4
- package/dist/public/deviceActions.js +4 -2
- package/dist/public/environment.d.ts +16 -13
- package/dist/public/environment.js +11 -9
- package/dist/public/filePickers.d.ts +6 -4
- package/dist/public/filePickers.js +3 -2
- package/dist/public/glass.d.ts +6 -3
- package/dist/public/glass.js +2 -1
- package/dist/public/glassValue.d.ts +3 -4
- package/dist/public/glassValue.js +3 -4
- package/dist/public/keyboard.d.ts +4 -1
- package/dist/public/keyboard.js +2 -1
- package/dist/public/navigationTransition.d.ts +5 -5
- package/dist/public/navigationTransition.js +2 -2
- package/dist/public/presentation.d.ts +8 -7
- package/dist/public/presentation.js +5 -5
- package/dist/public/runtime.d.ts +9 -8
- package/dist/public/runtime.js +7 -7
- package/dist/public/scroll.d.ts +6 -3
- package/dist/public/scroll.js +3 -1
- package/dist/public/structure.d.ts +2 -2
- package/dist/public/toolbar.d.ts +15 -11
- package/dist/public/toolbar.js +12 -7
- package/dist/reachability/model.d.ts +4 -3
- package/dist/reachability/react.d.ts +4 -1
- package/dist/reachability/react.js +2 -1
- package/dist/root/SatinRoot.d.ts +3 -3
- package/dist/root/SatinRoot.js +12 -36
- package/dist/root/mountSatin.d.ts +1 -1
- package/dist/root/mountSatin.js +26 -35
- package/dist/root/runtimeContext.d.ts +2 -1
- package/dist/root/runtimeContext.js +2 -1
- package/dist/safe-area/Overlay.d.ts +3 -2
- package/dist/safe-area/Overlay.js +2 -1
- package/dist/safe-area/SafeAreaInset.d.ts +11 -7
- package/dist/safe-area/SafeAreaInset.js +7 -6
- package/dist/safe-area/context.d.ts +3 -3
- package/dist/safe-area/context.js +1 -1
- package/dist/safe-area/displayCorners.d.ts +4 -2
- package/dist/safe-area/displayCorners.js +4 -2
- package/dist/safe-area/modifiers.d.ts +14 -10
- package/dist/safe-area/modifiers.js +8 -4
- package/dist/scroll/ScrollSurface.js +5 -1
- package/dist/scroll/ScrollTarget.d.ts +1 -1
- package/dist/scroll/ScrollTarget.js +1 -1
- package/dist/scroll/edgePolicy.d.ts +2 -2
- package/dist/scroll/position.d.ts +5 -3
- package/dist/scroll/types.d.ts +1 -1
- package/dist/search/PadSearchDrawer.d.ts +7 -0
- package/dist/search/PadSearchDrawer.js +8 -0
- package/dist/search/PadSearchPresentationContext.d.ts +6 -0
- package/dist/search/PadSearchPresentationContext.js +4 -0
- package/dist/search/SearchDrawer.d.ts +1 -1
- package/dist/search/SearchDrawer.js +6 -1
- package/dist/search/SearchSuggestionsPopover.d.ts +6 -0
- package/dist/search/SearchSuggestionsPopover.js +42 -0
- package/dist/search/SearchToolbarField.d.ts +2 -1
- package/dist/search/SearchToolbarField.js +28 -7
- package/dist/search/padSearchBarMotion.d.ts +69 -0
- package/dist/search/padSearchBarMotion.js +73 -0
- package/dist/search/padSearchFieldSamples.json +491 -0
- package/dist/search/searchPlacement.d.ts +3 -0
- package/dist/search/searchPlacement.js +7 -0
- package/dist/search/searchable.d.ts +14 -12
- package/dist/search/usePadSearchFieldMotion.d.ts +4 -0
- package/dist/search/usePadSearchFieldMotion.js +100 -0
- package/dist/search/useSearchPresentation.d.ts +2 -1
- package/dist/search/useSearchPresentation.js +23 -5
- package/dist/server.d.ts +4 -4
- package/dist/server.js +4 -4
- package/dist/shapes/corners.d.ts +3 -3
- package/dist/shapes/corners.js +1 -2
- package/dist/styles.css +8289 -8244
- package/dist/sw/vite.d.ts +4 -0
- package/dist/sw/vite.js +2 -0
- package/dist/symbols/Symbol.d.ts +7 -5
- package/dist/symbols/Symbol.js +5 -3
- package/dist/symbols/catalogue.d.ts +1 -1
- package/dist/symbols/types.d.ts +6 -4
- package/dist/tabs/TabSection.d.ts +3 -2
- package/dist/tabs/TabSection.js +2 -1
- package/dist/tabs/TabView.js +54 -51
- package/dist/tasks/useTask.d.ts +5 -4
- package/dist/tasks/useTask.js +4 -3
- package/dist/testing.d.ts +3 -2
- package/dist/testing.js +3 -2
- package/dist/text-input/focusChain.d.ts +4 -3
- package/dist/text-input/focusChain.js +3 -2
- package/dist/text-input/kinds.d.ts +1 -1
- package/dist/web/WebView.d.ts +10 -3
- package/dist/web/WebView.js +3 -2
- package/dist/web/authentication.d.ts +12 -1
- package/dist/web/authentication.js +2 -1
- package/dist/web/openURL.d.ts +10 -4
- package/dist/web/openURL.js +4 -2
- package/dist/web/useWebAuthentication.d.ts +2 -1
- package/dist/web/useWebAuthentication.js +2 -1
- package/dist/zoom/Zoomable.d.ts +3 -3
- package/dist/zoom/Zoomable.js +2 -2
- package/package.json +1 -1
package/dist/actions/rowScrub.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { cornerClipPath, roundedCornerPath } from '../shapes/corners.js';
|
|
2
|
-
import { takeoverSample } from './swipeSpring.js';
|
|
3
2
|
import { swipeEdge } from './types.js';
|
|
4
3
|
// iOS 27 matched phone reference, light/dark, 1/2/3 actions, both edges:
|
|
5
4
|
// color-mask width 60pt, gap 10pt, outer padding 10pt. Capsule 44pt layout / 42-43pt solid mask.
|
|
@@ -63,9 +62,16 @@ function inlineEntrance(distance, buttonWidth) {
|
|
|
63
62
|
}
|
|
64
63
|
return { scale, opacity };
|
|
65
64
|
}
|
|
65
|
+
/** A destructive takeover keeps the other actions visible and dimmed. Native tall
|
|
66
|
+
* Delete row (r3 receipts): More fades from 0.73 at 142pt revealed to 0.30 at 381pt. */
|
|
67
|
+
function destructiveSecondaryOpacity(distance) {
|
|
68
|
+
return clamp(0.73 - ((0.73 - 0.3) / (381 - 142)) * (distance - 142));
|
|
69
|
+
}
|
|
66
70
|
/** Spatial fit of native's sequential capsule reveal. Rest interpolates the
|
|
67
|
-
* contact pose into the fully revealed spring target; it is never a DOM writer.
|
|
68
|
-
|
|
71
|
+
* contact pose into the fully revealed spring target; it is never a DOM writer.
|
|
72
|
+
* A destructive takeover packs the other actions against the row and the primary
|
|
73
|
+
* fills the rest (native: primary = revealed - 80 on a tall two-action row). */
|
|
74
|
+
export function swipeActionPose(position, index, count, takeover = 0, rest = 0, buttonWidth = 60, inline = false, allowsFullSwipe = false, destructive = false) {
|
|
69
75
|
const distance = Math.abs(position);
|
|
70
76
|
const open = swipeOpenWidth(count, buttonWidth);
|
|
71
77
|
const excess = Math.max(0, distance - open);
|
|
@@ -77,15 +83,17 @@ export function swipeActionPose(position, index, count, takeover = 0, rest = 0,
|
|
|
77
83
|
const entrance = inline ? inlineEntrance(localDistance + revealLead, buttonWidth) : undefined;
|
|
78
84
|
const contactScale = entrance?.scale ?? revealScale(localDistance);
|
|
79
85
|
const reveal = contactScale + (clamp(distance / open) - contactScale) * rest;
|
|
80
|
-
const
|
|
86
|
+
const packed = destructive && index > 0 ? takeover * excess : 0;
|
|
87
|
+
const spread = (((index + 0.5) * excess) / (count + 0.5)) * (1 - takeover) + packed;
|
|
88
|
+
const opacity = (entrance?.opacity ?? contactScale) + (1 - (entrance?.opacity ?? contactScale)) * rest;
|
|
89
|
+
const takenOver = destructive ? destructiveSecondaryOpacity(distance) : 0;
|
|
81
90
|
return {
|
|
82
91
|
scale: reveal,
|
|
83
92
|
spread,
|
|
84
|
-
opacity:
|
|
85
|
-
(index === 0 ? 1 : 1 - takeover),
|
|
93
|
+
opacity: index === 0 ? opacity : opacity * (1 - takeover) + takenOver * takeover,
|
|
86
94
|
growth: index === 0
|
|
87
95
|
? (inline && allowsFullSwipe ? excess * (1 - takeover) : 0) +
|
|
88
|
-
takeover * Math.max(0, distance - buttonWidth - 20)
|
|
96
|
+
takeover * (destructive ? excess : Math.max(0, distance - buttonWidth - 20))
|
|
89
97
|
: 0,
|
|
90
98
|
};
|
|
91
99
|
}
|
|
@@ -102,6 +110,7 @@ export function swipeActionTracks(primary, count, width, allowsFullSwipe) {
|
|
|
102
110
|
const trailing = primary.parentElement?.dataset.satinSwipeTray === 'trailing';
|
|
103
111
|
const inline = primary.closest('[data-satin-row-actions]')?.dataset.satinSwipeLabelLayout ===
|
|
104
112
|
'inline';
|
|
113
|
+
const destructive = primary.dataset.satinSwipeRole === 'destructive';
|
|
105
114
|
return buttons.flatMap((button, index) => {
|
|
106
115
|
const buttonWidth = Number.parseFloat(getComputedStyle(button).width) || 60;
|
|
107
116
|
const middleWidth = Math.max(1, buttonWidth - capsuleHeight(primary));
|
|
@@ -118,7 +127,7 @@ export function swipeActionTracks(primary, count, width, allowsFullSwipe) {
|
|
|
118
127
|
: count === 1 || raw === Infinity
|
|
119
128
|
? 1
|
|
120
129
|
: progress * progress * (3 - 2 * progress));
|
|
121
|
-
return swipeActionPose(position, index, count, raw === Infinity ? takeover : takeover * (1 - rest), rest, buttonWidth, inline, allowsFullSwipe);
|
|
130
|
+
return swipeActionPose(position, index, count, raw === Infinity ? takeover : takeover * (1 - rest), rest, buttonWidth, inline, allowsFullSwipe, destructive);
|
|
122
131
|
};
|
|
123
132
|
return [
|
|
124
133
|
{
|
|
@@ -208,15 +217,20 @@ export function presentedTakeover(primary, position) {
|
|
|
208
217
|
const count = primary.parentElement?.children.length ?? 1;
|
|
209
218
|
const inline = primary.closest('[data-satin-row-actions]')?.dataset.satinSwipeLabelLayout ===
|
|
210
219
|
'inline';
|
|
211
|
-
const
|
|
212
|
-
const
|
|
220
|
+
const excess = Math.max(0, Math.abs(position) - swipeOpenWidth(count, buttonWidth));
|
|
221
|
+
const ordinary = inline ? excess : 0;
|
|
222
|
+
// A destructive takeover grows only into the excess; inline rows grow that much
|
|
223
|
+
// before takeover too, so their caught takeover reads as none.
|
|
224
|
+
const expanded = primary.dataset.satinSwipeRole === 'destructive'
|
|
225
|
+
? excess
|
|
226
|
+
: Math.max(0, Math.abs(position) - buttonWidth - 20);
|
|
213
227
|
if (expanded <= ordinary)
|
|
214
228
|
return 0;
|
|
215
229
|
const matrix = new DOMMatrixReadOnly(getComputedStyle(middle).transform);
|
|
216
230
|
const growth = (matrix.a - 1) * Math.max(1, buttonWidth - capsuleHeight(primary));
|
|
217
231
|
return clamp((growth - ordinary) / (expanded - ordinary));
|
|
218
232
|
}
|
|
219
|
-
export function createRowScrub(host, source, content, initial, width, configuration) {
|
|
233
|
+
export function createRowScrub(host, source, content, initial, width, configuration, contact) {
|
|
220
234
|
if (!width || source.scrollWidth <= source.clientWidth)
|
|
221
235
|
return undefined;
|
|
222
236
|
const widths = swipeWidths(host);
|
|
@@ -234,34 +248,18 @@ export function createRowScrub(host, source, content, initial, width, configurat
|
|
|
234
248
|
if (primary) {
|
|
235
249
|
const edge = swipeEdge(configuration, side);
|
|
236
250
|
const actions = swipeActionTracks(primary, edge.actions.length, width, edge.allowsFullSwipe);
|
|
237
|
-
const reducedMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;
|
|
238
|
-
const caught = initial ? presentedTakeover(primary, initial) : 0;
|
|
239
|
-
const state = { from: caught, target: caught, velocity: 0, started: performance.now() };
|
|
240
|
-
const threshold = fullSwipeThreshold(width, edge.actions.length, side, widths[side], host.dataset.satinSwipeLabelLayout === 'inline');
|
|
241
251
|
const openWidth = swipeOpenWidth(edge.actions.length, widths[side]);
|
|
242
|
-
let
|
|
252
|
+
let previousPosition = Number.NaN;
|
|
243
253
|
let previousTakeover = Number.NaN;
|
|
244
254
|
const update = (now, raw, position) => {
|
|
245
|
-
|
|
246
|
-
const target = edge.allowsFullSwipe && raw * sign >= threshold ? 1 : 0;
|
|
247
|
-
if (target !== state.target) {
|
|
248
|
-
state.from = sample.position;
|
|
249
|
-
state.velocity = sample.velocity;
|
|
250
|
-
state.target = target;
|
|
251
|
-
state.started = now;
|
|
252
|
-
}
|
|
253
|
-
// Both spring states advance across reversals, but the hidden tray has
|
|
255
|
+
// Both takeover springs advance across reversals, but the hidden tray has
|
|
254
256
|
// no pixels to update. Read the scroll source before either tray writes.
|
|
257
|
+
const takeover = contact.takeover(side, raw, now);
|
|
255
258
|
if (position * sign <= 0)
|
|
256
259
|
return;
|
|
257
|
-
|
|
258
|
-
? 1
|
|
259
|
-
: reducedMotion
|
|
260
|
-
? target
|
|
261
|
-
: Math.max(0, Math.min(1, sample.position));
|
|
262
|
-
if (raw === previousRaw && takeover === previousTakeover)
|
|
260
|
+
if (position === previousPosition && takeover === previousTakeover)
|
|
263
261
|
return;
|
|
264
|
-
|
|
262
|
+
previousPosition = position;
|
|
265
263
|
previousTakeover = takeover;
|
|
266
264
|
const rest = initial ? clamp(1 - Math.abs(raw - initialRaw) / openWidth) : 0;
|
|
267
265
|
for (const action of actions) {
|
|
@@ -279,10 +277,14 @@ export function createRowScrub(host, source, content, initial, width, configurat
|
|
|
279
277
|
// WebKit owns touch arbitration and the scroll offset. Sample it once per
|
|
280
278
|
// frame and present the row, trays, and fitted takeover together. Mixing a
|
|
281
279
|
// compositor row with main-thread pills exposes every delayed main-thread frame.
|
|
280
|
+
// The contact maps the raw offset, so a takeover's anchor moves row and pills alike.
|
|
282
281
|
let actionFrame = 0;
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
282
|
+
const rawPosition = () => initialRaw + origin - source.scrollLeft;
|
|
283
|
+
/** Present the current scroll sample. A release passes `evaluate: false`: lifting
|
|
284
|
+
* the finger cannot start a takeover that no frame has shown. */
|
|
285
|
+
const flush = (now = performance.now(), evaluate = true) => {
|
|
286
|
+
const raw = rawPosition();
|
|
287
|
+
const position = contact.present(raw, now, evaluate);
|
|
286
288
|
Object.assign(content.style, contentFrame(position, initial ? 1 : 0));
|
|
287
289
|
for (const update of actionUpdates)
|
|
288
290
|
update(now, raw, position);
|
|
@@ -295,8 +297,8 @@ export function createRowScrub(host, source, content, initial, width, configurat
|
|
|
295
297
|
paintActions(performance.now());
|
|
296
298
|
return {
|
|
297
299
|
flush,
|
|
298
|
-
rawPosition
|
|
299
|
-
position: () =>
|
|
300
|
+
rawPosition,
|
|
301
|
+
position: () => contact.present(rawPosition(), performance.now(), false),
|
|
300
302
|
cancel: () => {
|
|
301
303
|
cancelAnimationFrame(actionFrame);
|
|
302
304
|
// Outside contact, the settled side's visibility belongs to the row CSS.
|
package/dist/actions/types.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import type { SymbolName } from '../symbols/Symbol.js';
|
|
3
3
|
import type { CornerShape } from '../shapes/corners.js';
|
|
4
|
-
/**
|
|
4
|
+
/** An entry in a menu, context menu or swipe action list: a command, a titled section of
|
|
5
|
+
* entries, or a submenu. */
|
|
5
6
|
export type Action = ActionItem | Readonly<{
|
|
6
7
|
section: readonly Action[];
|
|
7
8
|
label?: string;
|
|
@@ -13,7 +14,8 @@ export type Action = ActionItem | Readonly<{
|
|
|
13
14
|
disabled?: boolean;
|
|
14
15
|
submenu: readonly Action[];
|
|
15
16
|
}>;
|
|
16
|
-
/** A
|
|
17
|
+
/** A single command with an id, a label, an optional symbol and role, and an `onPress` handler.
|
|
18
|
+
* Menus, swipe actions, alerts and confirmation dialogs all use it. */
|
|
17
19
|
export type ActionItem = Readonly<{
|
|
18
20
|
id: string;
|
|
19
21
|
label: string;
|
|
@@ -27,7 +29,8 @@ export type ActionItem = Readonly<{
|
|
|
27
29
|
onPress?: (() => void) | (() => Promise<void>);
|
|
28
30
|
tint?: string;
|
|
29
31
|
}>;
|
|
30
|
-
/** A swipe command may
|
|
32
|
+
/** A swipe action command. It may drop its visible label if it has a symbol or icon and an
|
|
33
|
+
* `accessibilityLabel`. */
|
|
31
34
|
export type SwipeActionItem = ActionItem | Readonly<Omit<ActionItem, 'label' | 'accessibilityLabel'> & {
|
|
32
35
|
label?: never;
|
|
33
36
|
accessibilityLabel: string;
|
|
@@ -36,18 +39,21 @@ export type SwipeActionItem = ActionItem | Readonly<Omit<ActionItem, 'label' | '
|
|
|
36
39
|
} | {
|
|
37
40
|
icon: ReactNode;
|
|
38
41
|
})>;
|
|
42
|
+
/** An entry in a row's swipe actions: any menu entry, or a swipe command that shows only a
|
|
43
|
+
* symbol. */
|
|
39
44
|
export type SwipeAction = Action | SwipeActionItem;
|
|
40
|
-
/** `swipeActions
|
|
45
|
+
/** The `swipeActions` prop of a row: actions for one edge, or a list with one entry per edge. */
|
|
41
46
|
export type SwipeActions = SwipeActionsConfiguration | readonly SwipeActionsConfiguration[];
|
|
42
|
-
/**
|
|
47
|
+
/** The swipe actions on one row edge, `trailing` by default. A full swipe runs the first action
|
|
48
|
+
* unless `allowsFullSwipe` is false. */
|
|
43
49
|
export type SwipeActionsConfiguration = Readonly<{
|
|
44
50
|
edge?: 'leading' | 'trailing';
|
|
45
|
-
/**
|
|
51
|
+
/** The shape of the revealed row face. Omit it for the system default. */
|
|
46
52
|
shape?: CornerShape;
|
|
47
53
|
allowsFullSwipe?: boolean;
|
|
48
54
|
actions: readonly SwipeAction[];
|
|
49
55
|
}>;
|
|
50
|
-
/** `contextMenu
|
|
56
|
+
/** The `contextMenu` prop of a row: a list of actions, or actions with a custom `preview`. */
|
|
51
57
|
export type ContextMenuConfiguration = readonly Action[] | Readonly<{
|
|
52
58
|
actions: readonly Action[];
|
|
53
59
|
preview?: ReactNode;
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { type InstallContext } from './installContext.js';
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
* context hook, the steps list and the tip, with the default page's words and look. Styles
|
|
5
|
-
* are scoped to `satin-` classes; `--satin-install-tint` overrides the app's tint.
|
|
6
|
-
*/
|
|
7
|
-
/** The install context, read once on the page (it does not change while the page is open). */
|
|
2
|
+
/** Returns the install context, read once when the page opens. It throws outside the install
|
|
3
|
+
* page. */
|
|
8
4
|
export declare function useInstallContext(): InstallContext;
|
|
9
|
-
/** The three steps
|
|
5
|
+
/** The three install steps for this device, browser and Safari layout, worded as on Satin's own
|
|
6
|
+
* install page. */
|
|
10
7
|
export declare function InstallSteps(): import("react").JSX.Element;
|
|
11
8
|
/**
|
|
12
9
|
* The tip at Safari's real control ("Page Menu, then Share" or "Share"), in Satin's glass.
|
|
@@ -6,12 +6,11 @@ import { deviceTokens } from './installExperienceDevice.js';
|
|
|
6
6
|
import { TIP_GLASS } from './installExperienceHome.js';
|
|
7
7
|
import { readInstallContext } from './installContext.js';
|
|
8
8
|
import { classifySafariBar, tipPath } from './installTipPath.js';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*/
|
|
14
|
-
/** The install context, read once on the page (it does not change while the page is open). */
|
|
9
|
+
// Optional building blocks for a custom install page (public through `@satinhq/kit/install`): the
|
|
10
|
+
// context hook, the steps list and the tip, with the default page's words and look. Styles
|
|
11
|
+
// are scoped to `satin-` classes; `--satin-install-tint` overrides the app's tint.
|
|
12
|
+
/** Returns the install context, read once when the page opens. It throws outside the install
|
|
13
|
+
* page. */
|
|
15
14
|
export function useInstallContext() {
|
|
16
15
|
const [context] = useState(readInstallContext);
|
|
17
16
|
return context;
|
|
@@ -52,7 +51,8 @@ function useAppTint(context) {
|
|
|
52
51
|
document.documentElement.style.setProperty('--satin-app-tint', context.app.tint);
|
|
53
52
|
}, [context.app.tint]);
|
|
54
53
|
}
|
|
55
|
-
/** The three steps
|
|
54
|
+
/** The three install steps for this device, browser and Safari layout, worded as on Satin's own
|
|
55
|
+
* install page. */
|
|
56
56
|
export function InstallSteps() {
|
|
57
57
|
const context = useInstallContext();
|
|
58
58
|
const bar = useSafariBar(context);
|
|
@@ -3,5 +3,6 @@ export { readInstallationFailure, type InstallationFailure } from './installatio
|
|
|
3
3
|
export type InstallationDiagnosticsProps = {
|
|
4
4
|
scope: string;
|
|
5
5
|
};
|
|
6
|
-
/**
|
|
6
|
+
/** Shows the app's installation receipts, for an optional diagnostics screen in Settings. Pass the
|
|
7
|
+
* app's scope. */
|
|
7
8
|
export declare function InstallationDiagnostics({ scope }: InstallationDiagnosticsProps): import("react").JSX.Element;
|
|
@@ -3,7 +3,8 @@ import { useEffect, useState } from 'react';
|
|
|
3
3
|
import { NavigationRow, Section, ValueRow } from '../list-form/ListForm.js';
|
|
4
4
|
import { readInstallationFailure, readInstallationSuccess } from './installationFailure.js';
|
|
5
5
|
export { readInstallationFailure } from './installationFailure.js';
|
|
6
|
-
/**
|
|
6
|
+
/** Shows the app's installation receipts, for an optional diagnostics screen in Settings. Pass the
|
|
7
|
+
* app's scope. */
|
|
7
8
|
export function InstallationDiagnostics({ scope }) {
|
|
8
9
|
const [failure, setFailure] = useState(() => readInstallationFailure(scope));
|
|
9
10
|
const [success, setSuccess] = useState(() => readInstallationSuccess(scope));
|
package/dist/app/appearance.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { type PreferredColorScheme } from './appearanceStore.js';
|
|
|
2
2
|
export declare const readDarkAppearance: () => boolean;
|
|
3
3
|
export declare const subscribeAppearance: (listener: () => void) => () => void;
|
|
4
4
|
export declare function useDarkAppearance(): boolean;
|
|
5
|
-
/**
|
|
5
|
+
/** Returns a short log of appearance changes and lifecycle events, for diagnosing appearance
|
|
6
|
+
* after a resume. */
|
|
6
7
|
export declare const readAppearanceDiagnostics: () => readonly Readonly<{
|
|
7
8
|
at: number;
|
|
8
9
|
event: string;
|
|
@@ -12,7 +13,8 @@ export declare const readAppearanceDiagnostics: () => readonly Readonly<{
|
|
|
12
13
|
eventDark?: boolean;
|
|
13
14
|
heldDark: boolean;
|
|
14
15
|
}>[];
|
|
15
|
-
/**
|
|
16
|
-
*
|
|
16
|
+
/** Forces the whole app into `light` or `dark`. `system` or null follows the system again. The
|
|
17
|
+
* override lasts until the page reloads, so store the user's choice with `useAppStorage` and
|
|
18
|
+
* apply it at launch. */
|
|
17
19
|
export declare function preferredColorScheme(scheme: PreferredColorScheme): void;
|
|
18
20
|
export type { PreferredColorScheme };
|
package/dist/app/appearance.js
CHANGED
|
@@ -6,10 +6,12 @@ export const subscribeAppearance = (listener) => store().subscribe(listener);
|
|
|
6
6
|
export function useDarkAppearance() {
|
|
7
7
|
return useSyncExternalStore(subscribeAppearance, readDarkAppearance, () => false);
|
|
8
8
|
}
|
|
9
|
-
/**
|
|
9
|
+
/** Returns a short log of appearance changes and lifecycle events, for diagnosing appearance
|
|
10
|
+
* after a resume. */
|
|
10
11
|
export const readAppearanceDiagnostics = () => store().getDiagnostics();
|
|
11
|
-
/**
|
|
12
|
-
*
|
|
12
|
+
/** Forces the whole app into `light` or `dark`. `system` or null follows the system again. The
|
|
13
|
+
* override lasts until the page reloads, so store the user's choice with `useAppStorage` and
|
|
14
|
+
* apply it at launch. */
|
|
13
15
|
export function preferredColorScheme(scheme) {
|
|
14
16
|
store().setPreferredColorScheme(scheme);
|
|
15
17
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** An appearance override for the whole app: `light`, `dark`, or `system` (or null) to follow the
|
|
2
|
+
* system. */
|
|
2
3
|
export type PreferredColorScheme = 'light' | 'dark' | 'system' | null;
|
|
3
4
|
export type AppearanceSnapshot = Readonly<{
|
|
4
5
|
dark: boolean;
|
|
5
6
|
pending: boolean;
|
|
6
7
|
}>;
|
|
7
|
-
/**
|
|
8
|
+
/** One entry in the appearance diagnostics log: what happened and the appearance seen then. */
|
|
8
9
|
export type AppearanceEvent = Readonly<{
|
|
9
10
|
at: number;
|
|
10
11
|
event: string;
|
package/dist/app/buildInfo.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
1
|
+
/** The framework version, build id, build time and build kind baked into the running bundle.
|
|
2
|
+
* The build id is unique per build. */
|
|
3
3
|
export type BuildInfo = Readonly<{
|
|
4
4
|
frameworkVersion: string;
|
|
5
5
|
appBuildId: string;
|
|
6
6
|
builtAt: string | null;
|
|
7
7
|
buildKind: 'development' | 'production' | 'unavailable';
|
|
8
8
|
}>;
|
|
9
|
-
/**
|
|
10
|
-
*
|
|
9
|
+
/** Returns the build that is running, even offline. The values are written by `satinApp` at
|
|
10
|
+
* build time; it never asks the server for the latest deployment. */
|
|
11
11
|
export declare function useBuildInfo(): BuildInfo;
|
package/dist/app/buildInfo.js
CHANGED
|
@@ -5,8 +5,8 @@ const buildInfo = Object.freeze(import.meta.env
|
|
|
5
5
|
builtAt: null,
|
|
6
6
|
buildKind: 'unavailable',
|
|
7
7
|
});
|
|
8
|
-
/**
|
|
9
|
-
*
|
|
8
|
+
/** Returns the build that is running, even offline. The values are written by `satinApp` at
|
|
9
|
+
* build time; it never asks the server for the latest deployment. */
|
|
10
10
|
export function useBuildInfo() {
|
|
11
11
|
return buildInfo;
|
|
12
12
|
}
|
package/dist/app/definition.d.ts
CHANGED
|
@@ -2,14 +2,18 @@ import type { ViewportFitPolicy } from './viewportValue.js';
|
|
|
2
2
|
import type { LaunchScreenSlot } from './launchScreen.js';
|
|
3
3
|
import type { ServerReachabilityConfig } from '../reachability/model.js';
|
|
4
4
|
declare const DEFINITION: unique symbol;
|
|
5
|
-
/** A
|
|
5
|
+
/** A capability the app declares so Satin sets up its runtime support: `push`, `badging` or
|
|
6
|
+
* `background-work`. */
|
|
6
7
|
export type AppCapability = 'badging' | 'background-work' | 'push';
|
|
7
8
|
/** An orientation that the app supports. */
|
|
8
9
|
export type AppOrientation = 'landscape' | 'portrait';
|
|
9
|
-
/**
|
|
10
|
+
/** What the app shows when the device is in an orientation it does not support: a rotation
|
|
11
|
+
* prompt, a letterboxed layout, a responsive layout, or nothing. */
|
|
10
12
|
export type OrientationFallback = 'letterbox' | 'none' | 'rotation-overlay' | 'responsive';
|
|
11
|
-
/** When a
|
|
13
|
+
/** When a downloaded update may apply: `cold-launch`, the default, or `cold-launch-or-explicit`,
|
|
14
|
+
* which also lets it apply on request, as the development menu does. */
|
|
12
15
|
export type UpdateActivation = 'cold-launch' | 'cold-launch-or-explicit';
|
|
16
|
+
/** One generated app icon file: its URL, pixel sizes, image type and purpose. */
|
|
13
17
|
export interface AppIcon {
|
|
14
18
|
readonly purpose?: 'any' | 'maskable' | 'monochrome';
|
|
15
19
|
readonly sizes: string;
|
|
@@ -72,12 +76,16 @@ export type AppDeploymentInput = AppDeploymentOptions & ({
|
|
|
72
76
|
readonly connectivity: 'server';
|
|
73
77
|
readonly reachability: ServerReachabilityConfig;
|
|
74
78
|
});
|
|
79
|
+
/** A validated app definition, returned by `defineApp`. Pass it to `runApp` and to the Vite
|
|
80
|
+
* plugin. */
|
|
75
81
|
export type AppDefinition = AppDeploymentInput & {
|
|
76
82
|
readonly [DEFINITION]: true;
|
|
77
83
|
};
|
|
78
84
|
export interface SatinAppShellPluginOptions {
|
|
79
85
|
readonly additionalPrecache: readonly string[];
|
|
80
86
|
}
|
|
87
|
+
/** Validates a full app definition and fills in its defaults. `defineApp` uses it; lab fixtures
|
|
88
|
+
* call it directly. */
|
|
81
89
|
export declare function defineSatinApp(input: AppDeploymentInput): AppDefinition;
|
|
82
90
|
export declare function generateSatinManifestJson(definition: AppDefinition, idiom?: 'phone' | 'pad'): string;
|
|
83
91
|
export declare function generatePreReactAppearanceScript(definition: AppDefinition): string;
|
package/dist/app/definition.js
CHANGED
|
@@ -185,6 +185,8 @@ function cloneReachability(value) {
|
|
|
185
185
|
surfaces: Object.freeze({ ...value.surfaces }),
|
|
186
186
|
});
|
|
187
187
|
}
|
|
188
|
+
/** Validates a full app definition and fills in its defaults. `defineApp` uses it; lab fixtures
|
|
189
|
+
* call it directly. */
|
|
188
190
|
export function defineSatinApp(input) {
|
|
189
191
|
assertRecord(input, 'app');
|
|
190
192
|
assertString(input.id, 'id');
|
package/dist/app/head.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AppDefinition } from './definition.js';
|
|
2
2
|
export { appManifestStem, appManifestFiles } from './manifests.js';
|
|
3
|
+
/** One app definition in a multi-install lab build, chosen by exact query parameters. */
|
|
3
4
|
export interface AppFixture {
|
|
4
5
|
/** Legacy display-mode capability rig only, never a production option. */
|
|
5
6
|
readonly display?: 'standalone' | 'fullscreen' | 'minimal-ui';
|
|
@@ -7,4 +8,4 @@ export interface AppFixture {
|
|
|
7
8
|
/** Exact query parameters for this lab-only installed app. First match wins. */
|
|
8
9
|
readonly query: Readonly<Record<string, string>>;
|
|
9
10
|
}
|
|
10
|
-
export declare function generateAppHead(fixtures: readonly AppFixture[], base: string, production?: boolean, devServer?: boolean): string;
|
|
11
|
+
export declare function generateAppHead(fixtures: readonly AppFixture[], base: string, production?: boolean, devServer?: boolean, covers?: ReadonlyMap<string, string>, buildId?: string): string;
|
package/dist/app/head.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { registerEarlyWorker } from './earlyWorker.js';
|
|
2
2
|
import { deploymentExpectation } from './deployment.js';
|
|
3
3
|
import { installLaunchWatchdog } from './launchWatchdog.js';
|
|
4
|
-
import { installLaunchScreenRuntime } from './launchScreenRuntime.js';
|
|
4
|
+
import { installLaunchScreenRuntime, resumeAppliedUpdate } from './launchScreenRuntime.js';
|
|
5
5
|
import { GLASS_LAUNCH_BUDGET_MS } from './launchReadiness.js';
|
|
6
6
|
import { appManifestHrefExpression, isIOSWebKit, installRouteForBase } from './install.js';
|
|
7
7
|
import { resolvePlatformGate } from './platformPolicy.js';
|
|
8
|
-
import {
|
|
8
|
+
import { launchScreenMarkup, prelaunchCSS } from './launchScreen.js';
|
|
9
9
|
import { installAppearanceStore } from './appearanceStore.js';
|
|
10
10
|
import { ADAPTIVE_LAYOUT_POLICY } from '../layout/layoutClass.js';
|
|
11
11
|
import { SATIN_VIEWPORT, resolveViewportFit } from './viewportValue.js';
|
|
12
12
|
import { STARTUP_SCREENS, startupImages } from './startup.js';
|
|
13
13
|
import { appManifestStem } from './manifests.js';
|
|
14
14
|
export { appManifestStem, appManifestFiles } from './manifests.js';
|
|
15
|
-
export function generateAppHead(fixtures, base, production = true, devServer = false) {
|
|
15
|
+
export function generateAppHead(fixtures, base, production = true, devServer = false, covers = new Map(), buildId) {
|
|
16
16
|
const appearance = fixtures[0]?.app.appearance;
|
|
17
17
|
const lightBackground = appearance?.background ?? '#ffffff';
|
|
18
18
|
const darkBackground = appearance?.darkLaunch === 'match-light'
|
|
@@ -20,6 +20,7 @@ export function generateAppHead(fixtures, base, production = true, devServer = f
|
|
|
20
20
|
: (appearance?.darkLaunch.background ?? '#000000');
|
|
21
21
|
const data = JSON.stringify(fixtures.map(({ app, query }) => ({
|
|
22
22
|
app,
|
|
23
|
+
cover: covers.get(app.id) ?? launchScreenMarkup(app),
|
|
23
24
|
query,
|
|
24
25
|
installRoute: installRouteForBase(app, base),
|
|
25
26
|
startupStem: appManifestStem(app),
|
|
@@ -27,7 +28,7 @@ export function generateAppHead(fixtures, base, production = true, devServer = f
|
|
|
27
28
|
}))).replaceAll('<', '\\u003c');
|
|
28
29
|
// A cached navigation must paint its launch surface even before head scripts
|
|
29
30
|
// run. Match the manifest's two backgrounds, including match-light apps.
|
|
30
|
-
return `<style id="satin-launch-paint">:root{--satin-launch-background:${lightBackground};--satin-launch-ink:#1c1c1e;--satin-background:${lightBackground}}@media(prefers-color-scheme:dark){:root{--satin-launch-background:${darkBackground};--satin-launch-ink:${appearance?.darkLaunch === 'match-light' ? '#1c1c1e' : '#ffffff'};--satin-background:${darkBackground}}}
|
|
31
|
+
return `<style id="satin-launch-paint">:root{--satin-launch-background:${lightBackground};--satin-launch-ink:#1c1c1e;--satin-background:${lightBackground}}@media(prefers-color-scheme:dark){:root{--satin-launch-background:${darkBackground};--satin-launch-ink:${appearance?.darkLaunch === 'match-light' ? '#1c1c1e' : '#ffffff'};--satin-background:${darkBackground}}}html,body{background:var(--satin-background);margin:0} [data-satin-root][data-satin-launch]:not([data-satin-launch="ready"]){opacity:0;pointer-events:none}</style>
|
|
31
32
|
<script id="satin-platform-gate">(() => {
|
|
32
33
|
performance.mark('satin-document-head');
|
|
33
34
|
const fixtures = ${data};
|
|
@@ -68,7 +69,13 @@ export function generateAppHead(fixtures, base, production = true, devServer = f
|
|
|
68
69
|
const fit = (${resolveViewportFit.toString()})(app.viewportFit || 'adaptive', navigator.userAgent, navigator.platform, navigator.maxTouchPoints, innerWidth, innerHeight, ${ADAPTIVE_LAYOUT_POLICY.horizontal.regularMinimum}, ${ADAPTIVE_LAYOUT_POLICY.vertical.regularMinimum});
|
|
69
70
|
document.querySelector('meta[name="viewport"]').content = ${JSON.stringify(SATIN_VIEWPORT)}.replace('viewport-fit=cover', 'viewport-fit=' + fit);
|
|
70
71
|
document.documentElement.dataset.satinViewportFit = fit;
|
|
71
|
-
(${installLaunchScreenRuntime.toString()})(
|
|
72
|
+
(${installLaunchScreenRuntime.toString()})({
|
|
73
|
+
markup: selected.cover, css: ${JSON.stringify(prelaunchCSS)}, scope: app.scope,
|
|
74
|
+
background: app.appearance.background,
|
|
75
|
+
darkBackground: app.appearance.darkLaunch === 'match-light' ? app.appearance.background : app.appearance.darkLaunch.background,
|
|
76
|
+
darkInk: app.appearance.darkLaunch === 'match-light' ? '#1c1c1e' : '#ffffff'
|
|
77
|
+
});
|
|
78
|
+
(${resumeAppliedUpdate.toString()})(app.scope, app.updateProfile === 'release' ? ${JSON.stringify(buildId)} : undefined);
|
|
72
79
|
(${installLaunchWatchdog.toString()})(${Math.max(10_000, GLASS_LAUNCH_BUDGET_MS)});
|
|
73
80
|
// First installation can overlap module loading. Existing registrations,
|
|
74
81
|
// with active or waiting versions, remain under the normal launch/update policy.
|
|
@@ -89,18 +96,7 @@ export function generateAppHead(fixtures, base, production = true, devServer = f
|
|
|
89
96
|
const darkBackground = app.appearance.darkLaunch === 'match-light' ? app.appearance.background : app.appearance.darkLaunch.background;
|
|
90
97
|
// CSS can select the cold background before the controller runs. Once
|
|
91
98
|
// installed, the shared controller owns every root color, in every session.
|
|
92
|
-
const field = document.createElement('style');
|
|
93
99
|
const darkInk = app.appearance.darkLaunch === 'match-light' ? '#1c1c1e' : '#ffffff';
|
|
94
|
-
field.textContent = [
|
|
95
|
-
':root{--satin-launch-background:' + app.appearance.background + ';--satin-launch-ink:#1c1c1e}',
|
|
96
|
-
'@media(prefers-color-scheme:dark){:root:not([data-satin-appearance]){',
|
|
97
|
-
'--satin-launch-background:' + darkBackground + ';--satin-launch-ink:' + darkInk + '}}',
|
|
98
|
-
':root[data-satin-appearance="light"]{--satin-launch-background:' + app.appearance.background,
|
|
99
|
-
';--satin-launch-ink:#1c1c1e}',
|
|
100
|
-
':root[data-satin-appearance="dark"]{--satin-launch-background:' + darkBackground,
|
|
101
|
-
';--satin-launch-ink:' + darkInk + '}',
|
|
102
|
-
].join('');
|
|
103
|
-
document.head.appendChild(field);
|
|
104
100
|
const store = (${installAppearanceStore.toString()})();
|
|
105
101
|
const apply = () => {
|
|
106
102
|
const dark = store.getSnapshot().dark;
|
|
@@ -109,6 +105,7 @@ export function generateAppHead(fixtures, base, production = true, devServer = f
|
|
|
109
105
|
document.documentElement.style.setProperty('--satin-launch-background', appearance.background);
|
|
110
106
|
document.documentElement.style.setProperty('--satin-launch-ink', dark ? darkInk : '#1c1c1e');
|
|
111
107
|
document.documentElement.style.setProperty('--satin-background', appearance.background);
|
|
108
|
+
window.__SATIN_PRELAUNCH__.appearance(appearance.background, dark ? darkInk : '#1c1c1e');
|
|
112
109
|
document.getElementById('satin-theme-color').content = appearance.background;
|
|
113
110
|
document.getElementById('satin-manifest').href = ${appManifestHrefExpression('selected.manifest')};
|
|
114
111
|
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Add these sources to a deployment's existing policy. Satin does not impose
|
|
2
|
+
* a site-wide CSP. Hashes cover exact final bytes, including the shadow style. */
|
|
3
|
+
export declare function inlinePolicySources(html: string, shadowStyle: string): {
|
|
4
|
+
scriptSrc: string[];
|
|
5
|
+
styleSrc: string[];
|
|
6
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
/** Add these sources to a deployment's existing policy. Satin does not impose
|
|
3
|
+
* a site-wide CSP. Hashes cover exact final bytes, including the shadow style. */
|
|
4
|
+
export function inlinePolicySources(html, shadowStyle) {
|
|
5
|
+
const digest = (value) => `'sha256-${createHash('sha256').update(value).digest('base64')}'`;
|
|
6
|
+
const scripts = new Set();
|
|
7
|
+
const styles = new Set([digest(shadowStyle)]);
|
|
8
|
+
for (const match of html.matchAll(/<script\b([^>]*)>([\s\S]*?)<\/script>/gi)) {
|
|
9
|
+
if (/\b(?:src|type\s*=\s*["']application\/json)/i.test(match[1]) || !match[2].trim())
|
|
10
|
+
continue;
|
|
11
|
+
scripts.add(digest(match[2]));
|
|
12
|
+
// Platform admission reinserts the original parser script. A hash policy
|
|
13
|
+
// must admit that script as well as the small admission wrapper.
|
|
14
|
+
const original = match[2].match(/const script = ("(?:[^"\\]|\\.)*");/);
|
|
15
|
+
if (original) {
|
|
16
|
+
const tag = JSON.parse(original[1]);
|
|
17
|
+
const body = tag.match(/<script\b([^>]*)>([\s\S]*?)<\/script>/i);
|
|
18
|
+
if (body && !/\bsrc\s*=/.test(body[1]) && body[2].trim())
|
|
19
|
+
scripts.add(digest(body[2]));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
for (const match of html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style>/gi))
|
|
23
|
+
styles.add(digest(match[1]));
|
|
24
|
+
return { scriptSrc: [...scripts], styleSrc: [...styles] };
|
|
25
|
+
}
|
package/dist/app/install.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare function appManifestHrefExpression(stemExpression: string): strin
|
|
|
11
11
|
export declare function installRouteForBase(app: AppDefinition, base: string): string;
|
|
12
12
|
/** Preview builds accept device/layout overrides; production never does. */
|
|
13
13
|
export interface InstallPageOptions {
|
|
14
|
+
readonly cover?: string;
|
|
14
15
|
readonly preview?: boolean;
|
|
15
16
|
/** The author's bundled install page, set by satinApp(app, { installPage }). */
|
|
16
17
|
readonly customPage?: string;
|
package/dist/app/install.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { installLaunchScreenRuntime } from './launchScreenRuntime.js';
|
|
2
|
+
import { launchScreenMarkup, prelaunchCSS } from './launchScreen.js';
|
|
1
3
|
import { resolveDeviceIdiom } from '../layout/deviceIdiom.js';
|
|
2
4
|
import { checkDeployment, deploymentExpectation } from './deployment.js';
|
|
3
5
|
import { installPageCheck } from './installCheck.js';
|
|
@@ -48,12 +50,13 @@ export function installPage(app, kind, base = '/', options = {}) {
|
|
|
48
50
|
.join('') +
|
|
49
51
|
'</noscript>'
|
|
50
52
|
: '';
|
|
51
|
-
const startup = `<script src="${app.scope}${stem}-startup.js"></script>${fallback}`;
|
|
53
|
+
const startup = `<script defer src="${app.scope}${stem}-startup.js"></script>${fallback}`;
|
|
52
54
|
const status = `<meta name="apple-mobile-web-app-capable" content="yes"><meta id="satin-install-status" name="apple-mobile-web-app-status-bar-style" content="default"><meta name="theme-color" content="${app.appearance.background}" media="(prefers-color-scheme: light)"><meta name="theme-color" content="${dark}" media="(prefers-color-scheme: dark)">`;
|
|
53
55
|
const title = kind === 'gate' ? 'This app is designed for iPhone and iPad' : app.name;
|
|
54
56
|
const body = `<p>${escape(app.description)}</p><a href="${escape(installRouteForBase(app, base))}">Add to Home Screen</a>`;
|
|
55
57
|
const head = `<meta charset="utf-8"><meta name="viewport" content="${SATIN_VIEWPORT}"><script>(()=>{const fit=(${resolveViewportFit.toString()})(${JSON.stringify(app.viewportFit ?? 'adaptive')},navigator.userAgent,navigator.platform,navigator.maxTouchPoints,innerWidth,innerHeight,${ADAPTIVE_LAYOUT_POLICY.horizontal.regularMinimum},${ADAPTIVE_LAYOUT_POLICY.vertical.regularMinimum});document.querySelector('meta[name="viewport"]').content=${JSON.stringify(SATIN_VIEWPORT)}.replace('viewport-fit=cover','viewport-fit='+fit)})();</script><title>${escape(title)}</title>${status}${startup}<link rel="manifest" href="${base}${stem}.webmanifest"><script>(()=>{document.querySelector('link[rel="manifest"]').href=${appManifestHrefExpression(JSON.stringify(base + stem))}})();</script><link rel="apple-touch-icon" href="${escape(icon.src)}">`;
|
|
56
|
-
const
|
|
58
|
+
const environment = `<script id="satin-prelaunch-install">(${installLaunchScreenRuntime.toString()})(${JSON.stringify({ markup: options.cover ?? launchScreenMarkup({ ...app, launchScreen: { icon: 'data:,' } }), css: prelaunchCSS, scope: app.scope, background: app.appearance.background, darkBackground: dark, darkInk: app.appearance.darkLaunch === 'match-light' ? '#1c1c1e' : '#fff', cover: false }).replaceAll('<', '\\u003c')});</script>`;
|
|
59
|
+
const scripts = `${kind === 'install' ? environment : ''}<script id="satin-deployment" type="application/json">${JSON.stringify([deploymentExpectation(app, base)]).replaceAll('<', '\\u003c')}</script>${kind === 'install' ? `<script data-satin-install-check${options.customPage ? ` data-satin-install-entry="${escape(options.customPage)}"` : ''}>(${installPageCheck.toString()})(${JSON.stringify(deploymentExpectation(app, base))},${checkDeployment.toString()},4000${options.customPage ? `,(${installCustomMount.toString()}).bind(null,${JSON.stringify(options.customPage)})` : ''}).catch(error=>{console.error("[Satin deployment]",error);window.__SATIN_PRELAUNCH__.error("Configuration check failed. Reload and check the console.","This app is misconfigured");document.getElementById("satin-install-guide").hidden=true})</script>` : ''}`;
|
|
57
60
|
if (options.customPage && kind === 'install') {
|
|
58
61
|
return customInstallPage({
|
|
59
62
|
head,
|