@vvedantb/native-ui 0.17.2 → 0.18.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/dist/AlertDialog.jsx +123 -0
- package/dist/AmbientBackground.jsx +73 -0
- package/dist/ArtifactList.jsx +59 -0
- package/dist/Avatar.jsx +285 -0
- package/dist/Badge.jsx +154 -0
- package/dist/BottomSheetContainer.jsx +208 -0
- package/dist/BulkActionBar.jsx +717 -0
- package/dist/Button.jsx +344 -0
- package/dist/CaptureButton.jsx +104 -0
- package/dist/Card.jsx +371 -0
- package/dist/Checkbox.jsx +177 -0
- package/dist/Chip.jsx +79 -0
- package/dist/ColorSwatches.jsx +159 -0
- package/dist/ConfirmDialog.jsx +157 -0
- package/dist/DateTimeField.jsx +204 -0
- package/dist/Dialog.jsx +614 -0
- package/dist/DividedList.jsx +120 -0
- package/dist/DropdownMenu.jsx +849 -0
- package/dist/EmptyState.jsx +434 -0
- package/dist/ExpoWebRoot.jsx +134 -0
- package/dist/FloatingFAB.jsx +95 -0
- package/dist/FloatingSurface.jsx +226 -0
- package/dist/FloatingTabBar.jsx +403 -0
- package/dist/HeaderIconButton.jsx +53 -0
- package/dist/IconButton.jsx +157 -0
- package/dist/Input.jsx +96 -0
- package/dist/Label.jsx +96 -0
- package/dist/LabeledSwitchRow.jsx +170 -0
- package/dist/LiquidGlass.jsx +2 -0
- package/dist/ListRow.jsx +281 -0
- package/dist/LoadingState.jsx +205 -0
- package/dist/PageIndicator.jsx +128 -0
- package/dist/PageTitle.jsx +205 -0
- package/dist/Pager.jsx +105 -0
- package/dist/PasswordInput.jsx +120 -0
- package/dist/PillTabs.jsx +300 -0
- package/dist/PinnedBotTile.jsx +212 -0
- package/dist/Progress.jsx +147 -0
- package/dist/SafeAreaContainer.jsx +38 -0
- package/dist/ScreenContainer.jsx +117 -0
- package/dist/SearchInput.jsx +150 -0
- package/dist/SectionLabel.jsx +128 -0
- package/dist/Select.jsx +223 -0
- package/dist/Separator.jsx +95 -0
- package/dist/Sheet.jsx +343 -0
- package/dist/SheetRow.jsx +198 -0
- package/dist/Skeleton.jsx +311 -0
- package/dist/Spinner.jsx +195 -0
- package/dist/StackPageHeader.jsx +160 -0
- package/dist/StatGrid.jsx +74 -0
- package/dist/StatusDot.jsx +129 -0
- package/dist/SurfaceRow.jsx +143 -0
- package/dist/SwipeableRow.jsx +50 -0
- package/dist/Switch.jsx +130 -0
- package/dist/Tabs.jsx +385 -0
- package/dist/Textarea.jsx +103 -0
- package/dist/ThemeToggle.jsx +78 -0
- package/dist/Toast.jsx +396 -0
- package/dist/ai/activity-step-detail.jsx +179 -0
- package/dist/ai/activity-step-label.js +816 -0
- package/dist/ai/activity-step-types.js +14 -0
- package/dist/ai/activity-step.jsx +98 -0
- package/dist/ai/activity-tasks-utils.js +172 -0
- package/dist/ai/activity-timeline.js +47 -0
- package/dist/ai/attachments.jsx +258 -0
- package/dist/ai/card-button.jsx +188 -0
- package/dist/ai/chain-of-thought.jsx +459 -0
- package/dist/ai/chat-jump-rail.jsx +57 -0
- package/dist/ai/chat-text.js +44 -0
- package/dist/ai/chat-thread.jsx +135 -0
- package/dist/ai/choice-question.jsx +925 -0
- package/dist/ai/code-block.jsx +574 -0
- package/dist/ai/collapsible.jsx +275 -0
- package/dist/ai/conversation.jsx +723 -0
- package/dist/ai/file-chip.jsx +198 -0
- package/dist/ai/icon-button.jsx +179 -0
- package/dist/ai/index.js +37 -0
- package/dist/ai/message-action-sheet.jsx +408 -0
- package/dist/ai/message-quote.jsx +241 -0
- package/dist/ai/message.jsx +739 -0
- package/dist/ai/prompt-input-menu.jsx +365 -0
- package/dist/ai/prompt-input.jsx +626 -0
- package/dist/ai/reasoning.jsx +446 -0
- package/dist/ai/reply-quote-chip.jsx +278 -0
- package/dist/ai/response-text.jsx +130 -0
- package/dist/ai/shimmer.jsx +149 -0
- package/dist/ai/sources.jsx +260 -0
- package/dist/ai/subagent-cards.jsx +645 -0
- package/dist/ai/suggestion.jsx +186 -0
- package/dist/ai/tool-approval-card.jsx +359 -0
- package/dist/ai/trace.jsx +728 -0
- package/dist/ai/unified-diff.jsx +280 -0
- package/dist/ai/use-streaming-disclosure.js +70 -0
- package/dist/auth/AuthProviders.jsx +27 -0
- package/dist/auth/AuthScreen.jsx +292 -0
- package/dist/auth/GoogleSignInButton.jsx +188 -0
- package/dist/auth/SecondFactorScreen.jsx +133 -0
- package/dist/auth/SignInScreen.jsx +482 -0
- package/dist/auth/authErrors.js +131 -0
- package/dist/auth/index.js +11 -0
- package/dist/auth/totp.js +378 -0
- package/dist/blur.js +36 -0
- package/dist/brand/AppIcons.jsx +118 -0
- package/dist/brand/AppMark.jsx +128 -0
- package/dist/brand/AppWordmark.jsx +138 -0
- package/dist/brand/GoogleMark.jsx +71 -0
- package/dist/brand/ProviderIcon.jsx +192 -0
- package/dist/brand/familyApps.js +33 -0
- package/dist/brand/index.js +11 -0
- package/dist/brand/svg.js +252 -0
- package/dist/brand/vProAppBenefits.js +19 -0
- package/dist/cn.js +5 -0
- package/dist/colors.js +15 -0
- package/dist/configureTheme.js +72 -0
- package/dist/controlStyles.js +520 -0
- package/dist/definiteSize.js +12 -0
- package/dist/dialogLayout.js +18 -0
- package/dist/entries/ai.js +4 -0
- package/dist/entries/ambient.js +3 -0
- package/dist/entries/auth.js +5 -0
- package/dist/entries/bottom-sheet.js +2 -0
- package/dist/entries/brand.js +2 -0
- package/dist/entries/button.js +2 -0
- package/dist/entries/date-time.js +3 -0
- package/dist/entries/expo-router.js +4 -0
- package/dist/entries/expo-web-root.js +3 -0
- package/dist/entries/floating.js +7 -0
- package/dist/entries/gesture-handler.js +4 -0
- package/dist/entries/keyboard-controller.js +4 -0
- package/dist/entries/layout.js +16 -0
- package/dist/entries/markdown.js +4 -0
- package/dist/entries/marketing.js +3 -0
- package/dist/entries/page-title.js +7 -0
- package/dist/entries/password-input.js +3 -0
- package/dist/entries/primitives.js +57 -0
- package/dist/entries/search-input.js +3 -0
- package/dist/entries/tab-bar.js +4 -0
- package/dist/entries/theme.js +7 -0
- package/dist/entries/toast.js +5 -0
- package/dist/entries/video.js +3 -0
- package/dist/entries/webview.js +4 -0
- package/dist/examples/AiChatGallery.jsx +509 -0
- package/dist/examples/ParityGallery.jsx +1307 -0
- package/dist/floating.js +62 -0
- package/dist/glassBlur.jsx +145 -0
- package/dist/gradient.js +17 -0
- package/dist/haptics.js +44 -0
- package/dist/highlight.js +382 -0
- package/dist/index.js +85 -0
- package/dist/initials.js +4 -0
- package/dist/loadingGrid.js +81 -0
- package/dist/markdown/Markdown.jsx +515 -0
- package/dist/markdown/MarkdownTable.jsx +443 -0
- package/dist/markdown/tableText.js +68 -0
- package/dist/marketing/PricingModal.jsx +608 -0
- package/dist/marketing/UpgradeToProCard.jsx +164 -0
- package/dist/marketing/pricing.js +34 -0
- package/dist/media/ImageGallery.jsx +580 -0
- package/dist/media/MediaCard.jsx +162 -0
- package/dist/media/imageGrid.js +54 -0
- package/dist/motion.js +33 -0
- package/dist/pagerSwipe.js +104 -0
- package/dist/palette.js +104 -0
- package/dist/primitives/button.jsx +127 -0
- package/dist/primitives/card.jsx +249 -0
- package/dist/primitives/icon.jsx +37 -0
- package/dist/primitives/input.jsx +56 -0
- package/dist/primitives/keyboard-controller.jsx +207 -0
- package/dist/primitives/keyboard-parts.jsx +75 -0
- package/dist/primitives/native-only-animated-view.jsx +68 -0
- package/dist/primitives/overlay.jsx +391 -0
- package/dist/primitives/pager-gesture.jsx +131 -0
- package/dist/primitives/select-host.jsx +103 -0
- package/dist/primitives/select.jsx +555 -0
- package/dist/primitives/sheet-drag.jsx +420 -0
- package/dist/primitives/sheet-gesture.jsx +219 -0
- package/dist/primitives/swipe-row-gesture.jsx +341 -0
- package/dist/primitives/switch.jsx +105 -0
- package/dist/primitives/text.jsx +145 -0
- package/dist/primitives/toast-drag-gesture.jsx +94 -0
- package/dist/primitives/use-controllable-state.js +40 -0
- package/dist/primitives/web-click-guard.js +69 -0
- package/dist/push.js +166 -0
- package/dist/relativeTime.js +70 -0
- package/dist/router/RouterStackPageHeader.jsx +53 -0
- package/dist/selectA11y.js +32 -0
- package/dist/settings/MoreFromProjectV.jsx +168 -0
- package/dist/settings/SettingsEmptyState.jsx +102 -0
- package/dist/settings/SettingsField.jsx +49 -0
- package/dist/settings/SettingsSection.jsx +101 -0
- package/dist/settings/SettingsStack.jsx +17 -0
- package/dist/settings/SettingsToggleRow.jsx +163 -0
- package/dist/settings/projectVMobileApps.js +126 -0
- package/dist/settings/relaxedSizeStyle.js +11 -0
- package/dist/sheetDrag.js +76 -0
- package/dist/svgA11y.js +46 -0
- package/dist/swipeRow.js +161 -0
- package/dist/tabBarRoutes.js +41 -0
- package/dist/tabIndicator.js +139 -0
- package/dist/tailwind-preset.cjs +255 -0
- package/dist/tailwind-preset.d.cts +55 -0
- package/dist/theme.jsx +62 -0
- package/dist/toastChrome.js +110 -0
- package/dist/toastDrag.js +51 -0
- package/dist/toastStore.js +54 -0
- package/dist/tokens.js +70 -0
- package/dist/typography.js +40 -0
- package/dist/useReduceMotion.js +32 -0
- package/dist/useReduceTransparency.js +27 -0
- package/dist/useScheme.js +15 -0
- package/dist/video/InlineVideo.jsx +115 -0
- package/dist/webview/HtmlView.jsx +169 -0
- package/dist/webview/HtmlView.web.jsx +63 -0
- package/dist/webview/htmlView.js +43 -0
- package/package.json +70 -33
- package/src/DropdownMenu.tsx +28 -16
- package/src/FloatingTabBar.tsx +78 -41
- package/src/ai/conversation.tsx +12 -8
- package/src/glassBlur.tsx +47 -22
- package/src/primitives/swipe-row-gesture.tsx +11 -2
package/dist/floating.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Platform } from "react-native";
|
|
2
|
+
import { tokens } from "./tokens";
|
|
3
|
+
export const FLOATING_INSET = tokens.spacing.unit;
|
|
4
|
+
export const FLOATING_TAB_BAR_HEIGHT = 64;
|
|
5
|
+
export const FLOATING_TAB_BAR_HEIGHT_LARGE = 72;
|
|
6
|
+
export const FLOATING_RADIUS = tokens.radius.card;
|
|
7
|
+
/**
|
|
8
|
+
* List rows: 20, the field radius before 0.3.3 (web fields are 10). Toasts
|
|
9
|
+
* used it until 0.3.12 and the upgrade card until 0.3.8; both are 16 now.
|
|
10
|
+
*/
|
|
11
|
+
export const FLOATING_ROW_RADIUS = 20;
|
|
12
|
+
export const FLOATING_PILL_RADIUS = tokens.radius.pill;
|
|
13
|
+
const FLOATING_BOX_SHADOW = "0px 8px 24px rgba(10, 10, 10, 0.12)";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Only the shadow keys, not `ViewStyle`: an Expo-widened `ViewStyle`
|
|
17
|
+
* (`position: "fixed"`, `backgroundImage`) is not assignable to a React
|
|
18
|
+
* Native 0.88 View `style`. A plain inferred type would be `{}` and lose
|
|
19
|
+
* `floatingShadow.shadowColor` and the rest for callers.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export const floatingShadow = Platform.select({
|
|
23
|
+
ios: {
|
|
24
|
+
shadowColor: "#0a0a0a",
|
|
25
|
+
shadowOffset: {
|
|
26
|
+
width: 0,
|
|
27
|
+
height: 8
|
|
28
|
+
},
|
|
29
|
+
shadowOpacity: 0.12,
|
|
30
|
+
shadowRadius: 20
|
|
31
|
+
},
|
|
32
|
+
default: {
|
|
33
|
+
boxShadow: FLOATING_BOX_SHADOW
|
|
34
|
+
}
|
|
35
|
+
}) ?? {};
|
|
36
|
+
export const floatingHairline = tokens.color.hairline;
|
|
37
|
+
export const floatingFill = tokens.color.fill;
|
|
38
|
+
|
|
39
|
+
/** The `tokens.color.surface` pair itself, so `configureTheme` reaches it. */
|
|
40
|
+
export const floatingSolidFill = tokens.color.surface;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Where `FloatingTabBar` sits: `FLOATING_INSET` in from the screen sides (and,
|
|
44
|
+
* in landscape, the notch) and above the home indicator. `BulkActionBar` with
|
|
45
|
+
* `placement="tabBar"` takes the same slot while the tab bar hides.
|
|
46
|
+
*/
|
|
47
|
+
export function tabBarSlotStyle(insets) {
|
|
48
|
+
return {
|
|
49
|
+
position: "absolute",
|
|
50
|
+
left: insets.left + FLOATING_INSET,
|
|
51
|
+
right: insets.right + FLOATING_INSET,
|
|
52
|
+
bottom: FLOATING_INSET + Math.max(insets.bottom, 8)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The height of `FloatingTabBar`, and of `BulkActionBar` in its slot. */
|
|
57
|
+
export function tabBarHeight(largerText) {
|
|
58
|
+
return largerText ? FLOATING_TAB_BAR_HEIGHT_LARGE : FLOATING_TAB_BAR_HEIGHT;
|
|
59
|
+
}
|
|
60
|
+
export function tabBarReserve(largerText, bottomInset) {
|
|
61
|
+
return tabBarHeight(largerText) + FLOATING_INSET + Math.max(bottomInset, 8) + 28;
|
|
62
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { c as _c } from "react/compiler-runtime";
|
|
2
|
+
import { createContext, useContext, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
3
|
+
import { StyleSheet, View } from "react-native";
|
|
4
|
+
import { loadBlur } from "./blur";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The current blur target, outside React state. A target change (each tab
|
|
8
|
+
* switch hands it from one tab to the next) then re-renders only the glass
|
|
9
|
+
* that subscribes, not the whole tab tree under the provider: as a context
|
|
10
|
+
* value it walked every mounted tab twice per switch.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
function createBlurTargetStore() {
|
|
14
|
+
let current = null;
|
|
15
|
+
const listeners = new Set();
|
|
16
|
+
return {
|
|
17
|
+
get: () => current,
|
|
18
|
+
set: next => {
|
|
19
|
+
if (next === current) return;
|
|
20
|
+
current = next;
|
|
21
|
+
for (const listener of listeners) listener();
|
|
22
|
+
},
|
|
23
|
+
subscribe: listener => {
|
|
24
|
+
listeners.add(listener);
|
|
25
|
+
return () => listeners.delete(listener);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// `null` outside a provider and inside a target (a blur view must not sit
|
|
31
|
+
// inside its own target). The value is the provider's one store, so it never
|
|
32
|
+
// changes.
|
|
33
|
+
const BlurTargetStoreContext = createContext(null);
|
|
34
|
+
const noSubscribe = () => () => {};
|
|
35
|
+
const noTarget = () => null;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The mounted `GlassBlurTarget` that floating glass blurs on Android, or
|
|
39
|
+
* `null` outside a `GlassBlurProvider` and inside the target itself.
|
|
40
|
+
*/
|
|
41
|
+
export function useGlassBlurTarget() {
|
|
42
|
+
const store = useContext(BlurTargetStoreContext);
|
|
43
|
+
return useSyncExternalStore(store?.subscribe ?? noSubscribe, store?.get ?? noTarget, noTarget);
|
|
44
|
+
}
|
|
45
|
+
export function GlassBlurProvider(t0) {
|
|
46
|
+
const $ = _c(3);
|
|
47
|
+
const {
|
|
48
|
+
children
|
|
49
|
+
} = t0;
|
|
50
|
+
const [store] = useState(createBlurTargetStore);
|
|
51
|
+
let t1;
|
|
52
|
+
if ($[0] !== children || $[1] !== store) {
|
|
53
|
+
t1 = <BlurTargetStoreContext.Provider value={store}>{children}</BlurTargetStoreContext.Provider>;
|
|
54
|
+
$[0] = children;
|
|
55
|
+
$[1] = store;
|
|
56
|
+
$[2] = t1;
|
|
57
|
+
} else {
|
|
58
|
+
t1 = $[2];
|
|
59
|
+
}
|
|
60
|
+
return t1;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Content that floating glass blurs on Android (an expo-blur
|
|
64
|
+
* `BlurTargetView`). Render the floating chrome outside it, inside the same
|
|
65
|
+
* `GlassBlurProvider`: a blur view must not sit inside its own target, so
|
|
66
|
+
* surfaces in here get no target. The last active target wins.
|
|
67
|
+
*/
|
|
68
|
+
export function GlassBlurTarget(t0) {
|
|
69
|
+
const $ = _c(15);
|
|
70
|
+
let children;
|
|
71
|
+
let props;
|
|
72
|
+
let style;
|
|
73
|
+
let t1;
|
|
74
|
+
if ($[0] !== t0) {
|
|
75
|
+
({
|
|
76
|
+
active: t1,
|
|
77
|
+
style,
|
|
78
|
+
children,
|
|
79
|
+
...props
|
|
80
|
+
} = t0);
|
|
81
|
+
$[0] = t0;
|
|
82
|
+
$[1] = children;
|
|
83
|
+
$[2] = props;
|
|
84
|
+
$[3] = style;
|
|
85
|
+
$[4] = t1;
|
|
86
|
+
} else {
|
|
87
|
+
children = $[1];
|
|
88
|
+
props = $[2];
|
|
89
|
+
style = $[3];
|
|
90
|
+
t1 = $[4];
|
|
91
|
+
}
|
|
92
|
+
const active = t1 === undefined ? true : t1;
|
|
93
|
+
const store = useContext(BlurTargetStoreContext);
|
|
94
|
+
const ref = useRef(null);
|
|
95
|
+
const BlurTargetView = loadBlur()?.BlurTargetView;
|
|
96
|
+
let t2;
|
|
97
|
+
let t3;
|
|
98
|
+
if ($[5] !== active || $[6] !== store) {
|
|
99
|
+
t2 = () => {
|
|
100
|
+
if (!store || !active) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
store.set(ref);
|
|
104
|
+
return () => {
|
|
105
|
+
if (store.get() === ref) {
|
|
106
|
+
store.set(null);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
t3 = [store, active];
|
|
111
|
+
$[5] = active;
|
|
112
|
+
$[6] = store;
|
|
113
|
+
$[7] = t2;
|
|
114
|
+
$[8] = t3;
|
|
115
|
+
} else {
|
|
116
|
+
t2 = $[7];
|
|
117
|
+
t3 = $[8];
|
|
118
|
+
}
|
|
119
|
+
useLayoutEffect(t2, t3);
|
|
120
|
+
let t4;
|
|
121
|
+
if ($[9] !== children) {
|
|
122
|
+
t4 = <BlurTargetStoreContext.Provider value={null}>{children}</BlurTargetStoreContext.Provider>;
|
|
123
|
+
$[9] = children;
|
|
124
|
+
$[10] = t4;
|
|
125
|
+
} else {
|
|
126
|
+
t4 = $[10];
|
|
127
|
+
}
|
|
128
|
+
const content = t4;
|
|
129
|
+
let t5;
|
|
130
|
+
if ($[11] !== content || $[12] !== props || $[13] !== style) {
|
|
131
|
+
t5 = BlurTargetView ? <BlurTargetView ref={ref} style={[styles.fill, style]} {...props}>{content}</BlurTargetView> : <View ref={ref} style={[styles.fill, style]} {...props}>{content}</View>;
|
|
132
|
+
$[11] = content;
|
|
133
|
+
$[12] = props;
|
|
134
|
+
$[13] = style;
|
|
135
|
+
$[14] = t5;
|
|
136
|
+
} else {
|
|
137
|
+
t5 = $[14];
|
|
138
|
+
}
|
|
139
|
+
return t5;
|
|
140
|
+
}
|
|
141
|
+
const styles = StyleSheet.create({
|
|
142
|
+
fill: {
|
|
143
|
+
flex: 1
|
|
144
|
+
}
|
|
145
|
+
});
|
package/dist/gradient.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
let expoLinearGradient;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `expo-linear-gradient`, or `null` when the optional peer is not installed.
|
|
5
|
+
* Metro bundles a `require` placed directly in a `try` block as an optional
|
|
6
|
+
* dependency, so a missing peer does not break the bundle.
|
|
7
|
+
*/
|
|
8
|
+
export function loadLinearGradient() {
|
|
9
|
+
if (expoLinearGradient === undefined) {
|
|
10
|
+
try {
|
|
11
|
+
expoLinearGradient = require("expo-linear-gradient");
|
|
12
|
+
} catch {
|
|
13
|
+
expoLinearGradient = null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return expoLinearGradient;
|
|
17
|
+
}
|
package/dist/haptics.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Platform } from "react-native";
|
|
2
|
+
let expoHaptics;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `expo-haptics`, or `null` when the optional peer is not installed. Metro
|
|
6
|
+
* bundles a `require` placed directly in a `try` block as an optional
|
|
7
|
+
* dependency, so a missing peer does not break the bundle.
|
|
8
|
+
*/
|
|
9
|
+
export function loadHaptics() {
|
|
10
|
+
if (expoHaptics === undefined) {
|
|
11
|
+
try {
|
|
12
|
+
expoHaptics = require("expo-haptics");
|
|
13
|
+
} catch {
|
|
14
|
+
expoHaptics = null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return expoHaptics;
|
|
18
|
+
}
|
|
19
|
+
function native() {
|
|
20
|
+
return Platform.OS === "web" ? null : loadHaptics();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A value ticks past a step: a tab, a segment, a checkbox. */
|
|
24
|
+
export function selectionHaptic() {
|
|
25
|
+
void native()?.selectionAsync();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A physical moment: `"light"` when something snaps home (a dragged sheet
|
|
30
|
+
* springing back), `"medium"` when a destructive action fires. Fire it in the
|
|
31
|
+
* same frame as the visual, once per user action.
|
|
32
|
+
*/
|
|
33
|
+
export function impactHaptic(weight) {
|
|
34
|
+
const haptics = native();
|
|
35
|
+
if (!haptics) return;
|
|
36
|
+
void haptics.impactAsync(weight === "light" ? haptics.ImpactFeedbackStyle.Light : haptics.ImpactFeedbackStyle.Medium);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** An operation the user started succeeded or failed (the success and error toasts). */
|
|
40
|
+
export function notificationHaptic(outcome) {
|
|
41
|
+
const haptics = native();
|
|
42
|
+
if (!haptics) return;
|
|
43
|
+
void haptics.notificationAsync(outcome === "success" ? haptics.NotificationFeedbackType.Success : haptics.NotificationFeedbackType.Error);
|
|
44
|
+
}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
let prismReactRenderer;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `prism-react-renderer`, or `null` when the optional peer is not installed.
|
|
5
|
+
* `CodeBlock` highlights with it. Metro bundles a `require` placed directly in
|
|
6
|
+
* a `try` block as an optional dependency, so a missing peer does not break
|
|
7
|
+
* the bundle.
|
|
8
|
+
*/
|
|
9
|
+
export function loadPrismReactRenderer() {
|
|
10
|
+
if (prismReactRenderer === undefined) {
|
|
11
|
+
try {
|
|
12
|
+
prismReactRenderer = require("prism-react-renderer");
|
|
13
|
+
} catch {
|
|
14
|
+
prismReactRenderer = null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// `?? null`: with the real peer types installed, TypeScript keeps `undefined`
|
|
18
|
+
// in the type after the `try` (the package typecheck only sees a stub).
|
|
19
|
+
return prismReactRenderer ?? null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The shiki `github-light` and `github-dark` colours the web `CodeBlock`
|
|
23
|
+
* uses, by role. Plain text is the theme foreground.
|
|
24
|
+
*/
|
|
25
|
+
export const GITHUB_CODE_COLORS = {
|
|
26
|
+
light: {
|
|
27
|
+
plain: "#24292e",
|
|
28
|
+
comment: "#6a737d",
|
|
29
|
+
keyword: "#d73a49",
|
|
30
|
+
string: "#032f62",
|
|
31
|
+
constant: "#005cc5",
|
|
32
|
+
function: "#6f42c1",
|
|
33
|
+
tag: "#22863a",
|
|
34
|
+
variable: "#e36209",
|
|
35
|
+
inserted: "#22863a",
|
|
36
|
+
deleted: "#b31d28"
|
|
37
|
+
},
|
|
38
|
+
dark: {
|
|
39
|
+
plain: "#e1e4e8",
|
|
40
|
+
comment: "#6a737d",
|
|
41
|
+
keyword: "#f97583",
|
|
42
|
+
string: "#9ecbff",
|
|
43
|
+
constant: "#79b8ff",
|
|
44
|
+
function: "#b392f0",
|
|
45
|
+
tag: "#85e89d",
|
|
46
|
+
variable: "#ffab70",
|
|
47
|
+
inserted: "#85e89d",
|
|
48
|
+
deleted: "#fdaeb7"
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** Prism token types to their GitHub role. Types not listed are plain. */
|
|
53
|
+
const PRISM_ROLE = {
|
|
54
|
+
comment: "comment",
|
|
55
|
+
prolog: "comment",
|
|
56
|
+
doctype: "comment",
|
|
57
|
+
cdata: "comment",
|
|
58
|
+
keyword: "keyword",
|
|
59
|
+
operator: "keyword",
|
|
60
|
+
atrule: "keyword",
|
|
61
|
+
important: "keyword",
|
|
62
|
+
rule: "keyword",
|
|
63
|
+
string: "string",
|
|
64
|
+
char: "string",
|
|
65
|
+
"attr-value": "string",
|
|
66
|
+
regex: "string",
|
|
67
|
+
url: "string",
|
|
68
|
+
"template-string": "string",
|
|
69
|
+
number: "constant",
|
|
70
|
+
boolean: "constant",
|
|
71
|
+
constant: "constant",
|
|
72
|
+
property: "constant",
|
|
73
|
+
"attr-name": "constant",
|
|
74
|
+
symbol: "constant",
|
|
75
|
+
builtin: "constant",
|
|
76
|
+
entity: "constant",
|
|
77
|
+
function: "function",
|
|
78
|
+
"function-variable": "function",
|
|
79
|
+
"class-name": "function",
|
|
80
|
+
maybe_class_name: "function",
|
|
81
|
+
tag: "tag",
|
|
82
|
+
selector: "tag",
|
|
83
|
+
variable: "variable",
|
|
84
|
+
parameter: "variable",
|
|
85
|
+
inserted: "inserted",
|
|
86
|
+
deleted: "deleted",
|
|
87
|
+
punctuation: "plain",
|
|
88
|
+
plain: "plain"
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The colour of a Prism token from its types. Later types win, as in
|
|
93
|
+
* `prism-react-renderer`'s theme lookup: the `<` of a tag (`["tag",
|
|
94
|
+
* "punctuation"]`) is plain.
|
|
95
|
+
*/
|
|
96
|
+
export function codeTokenColor(types, scheme) {
|
|
97
|
+
return GITHUB_CODE_COLORS[scheme][prismRole(types)];
|
|
98
|
+
}
|
|
99
|
+
function prismRole(types) {
|
|
100
|
+
let role = "plain";
|
|
101
|
+
for (const type of types) role = PRISM_ROLE[type] ?? role;
|
|
102
|
+
return role;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** One coloured run of code. */
|
|
106
|
+
|
|
107
|
+
/** A Prism token as `prism-react-renderer` hands it over: its types, outermost first. */
|
|
108
|
+
|
|
109
|
+
/** Languages `scriptTokens` refines: the JavaScript family, as Shiki's grammars. */
|
|
110
|
+
export const SCRIPT_LANGUAGES = new Set(["js", "jsx", "javascript", "mjs", "cjs", "ts", "tsx", "typescript", "mts", "cts"]);
|
|
111
|
+
|
|
112
|
+
/** Languages `shellTokens` colours. `prism-react-renderer` bundles no shell grammar. */
|
|
113
|
+
export const SHELL_LANGUAGES = new Set(["bash", "sh", "shell", "zsh", "shellscript", "console"]);
|
|
114
|
+
|
|
115
|
+
/** Keywords Shiki colours as constants (`constant.language`, `variable.language`, a `void` type). */
|
|
116
|
+
const SCRIPT_CONSTANT_KEYWORDS = new Set(["null", "undefined", "this", "super", "void"]);
|
|
117
|
+
const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
118
|
+
|
|
119
|
+
/** A token's role before context: what Prism's types say, with the Shiki differences that need no context. */
|
|
120
|
+
function scriptBaseRole({
|
|
121
|
+
types,
|
|
122
|
+
content
|
|
123
|
+
}) {
|
|
124
|
+
// Inside `${…}` Shiki keeps the punctuation in the string colour and the code in its own.
|
|
125
|
+
const interpolation = types.lastIndexOf("interpolation");
|
|
126
|
+
if (interpolation >= 0) {
|
|
127
|
+
const inner = types.slice(interpolation + 1);
|
|
128
|
+
return inner.includes("punctuation") || inner.includes("interpolation-punctuation") ? "string" : prismRole(inner);
|
|
129
|
+
}
|
|
130
|
+
// A regex character class, brackets and escapes too.
|
|
131
|
+
if (types.includes("char-class")) return "constant";
|
|
132
|
+
const role = prismRole(types);
|
|
133
|
+
if (role === "keyword" && SCRIPT_CONSTANT_KEYWORDS.has(content)) return "constant";
|
|
134
|
+
if (types.includes("builtin") && content === "console") return "plain";
|
|
135
|
+
if (types.includes("function") && content === "constructor") return "keyword";
|
|
136
|
+
return role;
|
|
137
|
+
}
|
|
138
|
+
/** Splits a token so escapes in strings and each word of plain text get a role of their own. */
|
|
139
|
+
function splitPiece(piece) {
|
|
140
|
+
const parts = piece.role === "string" && !piece.types.includes("regex") ? piece.content.split(/(\\.)/) : piece.role === "plain" ? piece.content.split(/([A-Za-z_$][\w$]*)/) : [piece.content];
|
|
141
|
+
return parts.filter(part => part !== "").map(part => ({
|
|
142
|
+
...piece,
|
|
143
|
+
content: part,
|
|
144
|
+
role: piece.role === "string" && /^\\./.test(part) ? "constant" : piece.role
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
const isWord = (piece, word) => piece?.content === word;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Prism tokens for the JavaScript family, recoloured where Prism and the
|
|
151
|
+
* web's Shiki grammars disagree, so the phone draws the web colours:
|
|
152
|
+
* `null`, `undefined` and `this` as constants; escapes in strings; a regex
|
|
153
|
+
* character class; `${…}` punctuation in the string colour; a `const` name
|
|
154
|
+
* as a constant; parameters in the variable colour; type and class names
|
|
155
|
+
* (a capitalised word not followed by `.`) as functions; ALL_CAPS words as
|
|
156
|
+
* constants. Returns lines of runs, adjacent runs of one role merged.
|
|
157
|
+
*/
|
|
158
|
+
export function scriptTokens(lines) {
|
|
159
|
+
const pieces = lines.flatMap((tokens, line) => tokens.filter(token => !token.empty && token.content !== "").flatMap(token => splitPiece({
|
|
160
|
+
content: token.content,
|
|
161
|
+
types: token.types,
|
|
162
|
+
role: scriptBaseRole(token),
|
|
163
|
+
line
|
|
164
|
+
})));
|
|
165
|
+
// Indices of the pieces that are not whitespace.
|
|
166
|
+
const sig = pieces.flatMap((piece, index) => piece.content.trim() === "" ? [] : [index]);
|
|
167
|
+
const at = position => {
|
|
168
|
+
const index = sig[position];
|
|
169
|
+
return index === undefined ? undefined : pieces[index];
|
|
170
|
+
};
|
|
171
|
+
const isIdentifier = piece => piece !== undefined && piece.role === "plain" && IDENTIFIER.test(piece.content);
|
|
172
|
+
const closing = open => {
|
|
173
|
+
let depth = 0;
|
|
174
|
+
for (let position = open; position < sig.length; position += 1) {
|
|
175
|
+
const content = at(position)?.content;
|
|
176
|
+
if (content === "(") depth += 1;else if (content === ")") {
|
|
177
|
+
depth -= 1;
|
|
178
|
+
if (depth === 0) return position;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return undefined;
|
|
182
|
+
};
|
|
183
|
+
// An arrow function's parameters, or a declared function's or method's.
|
|
184
|
+
const isParameterList = (open, close) => {
|
|
185
|
+
const before = at(open - 1);
|
|
186
|
+
if (isWord(before, "function") || isWord(before, "constructor")) return true;
|
|
187
|
+
if (before?.role === "function" && isWord(at(open - 2), "function")) return true;
|
|
188
|
+
let depth = 0;
|
|
189
|
+
for (let position = close + 1; position < Math.min(sig.length, close + 40); position += 1) {
|
|
190
|
+
const content = at(position)?.content;
|
|
191
|
+
if (position === close + 1 && content !== ":") return content === "=>" || content === "{" && before?.role === "function";
|
|
192
|
+
if (content === "(" || content === "<" || content === "[") depth += 1;else if (content === ")" || content === ">" || content === "]") depth -= 1;else if (depth === 0 && content === "=>") return true;else if (depth === 0 && content === "{") return before?.role === "function";else if (depth === 0 && (content === ";" || content === "=")) return false;
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
};
|
|
196
|
+
for (let position = 0; position < sig.length; position += 1) {
|
|
197
|
+
if (!isWord(at(position), "(")) continue;
|
|
198
|
+
const close = closing(position);
|
|
199
|
+
if (close === undefined || !isParameterList(position, close)) continue;
|
|
200
|
+
let depth = 0;
|
|
201
|
+
let expectName = true;
|
|
202
|
+
for (let inner = position + 1; inner < close; inner += 1) {
|
|
203
|
+
const piece = at(inner);
|
|
204
|
+
const content = piece?.content;
|
|
205
|
+
if (content === "(" || content === "[" || content === "{" || content === "<") depth += 1;else if (content === ")" || content === "]" || content === "}" || content === ">") depth -= 1;else if (depth === 0 && content === ",") expectName = true;else if (depth === 0 && (content === ":" || content === "=" || content === "?")) expectName = false;else if (depth === 0 && expectName && isIdentifier(piece)) {
|
|
206
|
+
piece.role = "variable";
|
|
207
|
+
expectName = false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
let inImport = false;
|
|
212
|
+
let constPattern = 0;
|
|
213
|
+
// `Map<string, number>`: Shiki draws type-argument brackets plain, Prism as operators.
|
|
214
|
+
let generic = 0;
|
|
215
|
+
for (let position = 0; position < sig.length; position += 1) {
|
|
216
|
+
const piece = at(position);
|
|
217
|
+
if (!piece) continue;
|
|
218
|
+
const previous = at(position - 1);
|
|
219
|
+
if (piece.role === "keyword" && piece.content === "import") inImport = true;
|
|
220
|
+
if (piece.role === "keyword" && piece.content === "from") inImport = false;
|
|
221
|
+
// `const { a, b } = …`: every name in the pattern.
|
|
222
|
+
if (isWord(previous, "const") && (piece.content === "{" || piece.content === "[")) constPattern = 1;else if (constPattern > 0 && (piece.content === "{" || piece.content === "[")) constPattern += 1;else if (constPattern > 0 && (piece.content === "}" || piece.content === "]")) constPattern -= 1;
|
|
223
|
+
if (piece.role === "keyword" && piece.content === "<" && pieces[(sig[position] ?? 0) - 1]?.role === "function") {
|
|
224
|
+
piece.role = "plain";
|
|
225
|
+
generic += 1;
|
|
226
|
+
} else if (generic > 0 && piece.role === "keyword" && piece.content === ">") {
|
|
227
|
+
piece.role = "plain";
|
|
228
|
+
generic -= 1;
|
|
229
|
+
}
|
|
230
|
+
if (!isIdentifier(piece) || inImport) continue;
|
|
231
|
+
const next = at(position + 1);
|
|
232
|
+
if (previous?.role === "keyword" && previous.content === "const" || constPattern > 0) piece.role = "constant";else if (isWord(next, "=>")) piece.role = "variable";else if (/^[A-Z][A-Z0-9_]+$/.test(piece.content)) piece.role = "constant";else if (/^[A-Z]/.test(piece.content) && /[a-z]/.test(piece.content) && !isWord(next, ".")) piece.role = "function";
|
|
233
|
+
}
|
|
234
|
+
return mergeLines(pieces, lines.length);
|
|
235
|
+
}
|
|
236
|
+
function mergeLines(pieces, count) {
|
|
237
|
+
const out = Array.from({
|
|
238
|
+
length: count
|
|
239
|
+
}, () => []);
|
|
240
|
+
for (const {
|
|
241
|
+
content,
|
|
242
|
+
role,
|
|
243
|
+
line
|
|
244
|
+
} of pieces) {
|
|
245
|
+
const runs = out[line];
|
|
246
|
+
if (!runs) continue;
|
|
247
|
+
const last = runs.at(-1);
|
|
248
|
+
if (last && last.role === role) last.content += content;else runs.push({
|
|
249
|
+
content,
|
|
250
|
+
role
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
return out;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Shell words Shiki colours as keywords. */
|
|
257
|
+
const SHELL_KEYWORDS = new Set(["if", "then", "else", "elif", "fi", "for", "while", "until", "do", "done", "case", "esac", "in", "function", "select", "break", "continue", "return", "export", "local", "readonly", "declare"]);
|
|
258
|
+
|
|
259
|
+
/** After these a new command starts. */
|
|
260
|
+
const SHELL_COMMAND_KEYWORDS = new Set(["then", "else", "do", "if", "while", "until", "elif"]);
|
|
261
|
+
|
|
262
|
+
/** Shell builtins Shiki colours as constants (`support.function.builtin`). */
|
|
263
|
+
const SHELL_BUILTINS = new Set(["echo", "cd", "printf", "source", "set", "unset", "exit", "pwd", "read", "eval", "exec", "shift", "trap", "wait", "type", "alias", "unalias", "true", "false", "test", "ulimit", "umask", "kill"]);
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Shell code in the roles of the web's Shiki `bash` grammar, for `bash`, `sh`,
|
|
267
|
+
* `shell` and `zsh` blocks: a command name as a function, its arguments as
|
|
268
|
+
* strings, `-x` and `--flag` options and numbers as constants, builtins
|
|
269
|
+
* (`echo`, `cd`) as constants, keywords, `|`, `||` and redirects as
|
|
270
|
+
* keywords, `$VAR` plain (also inside a double-quoted string), comments.
|
|
271
|
+
* `prism-react-renderer` bundles no shell grammar, so this needs no peer.
|
|
272
|
+
*/
|
|
273
|
+
export function shellTokens(code) {
|
|
274
|
+
return code.split("\n").map(source => {
|
|
275
|
+
const runs = [];
|
|
276
|
+
const push = (content, role) => {
|
|
277
|
+
if (content === "") return;
|
|
278
|
+
const last = runs.at(-1);
|
|
279
|
+
if (last && last.role === role) last.content += content;else runs.push({
|
|
280
|
+
content,
|
|
281
|
+
role
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
let index = 0;
|
|
285
|
+
let command = true;
|
|
286
|
+
// `for NAME in`: the name is plain, then `in` is a keyword.
|
|
287
|
+
let forState = "none";
|
|
288
|
+
// Inside `[ … ]` Shiki draws test operators (`-f`) as keywords and words plain.
|
|
289
|
+
let inTest = false;
|
|
290
|
+
while (index < source.length) {
|
|
291
|
+
const rest = source.slice(index);
|
|
292
|
+
const space = /^\s+/.exec(rest);
|
|
293
|
+
if (space) {
|
|
294
|
+
push(space[0], "plain");
|
|
295
|
+
index += space[0].length;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (rest.startsWith("#")) {
|
|
299
|
+
push(rest, "comment");
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
if (rest.startsWith('"')) {
|
|
303
|
+
// Double quotes: the string colour, with `$VAR` and `${VAR}` names plain.
|
|
304
|
+
const end = rest.slice(1).search(/(?<!\\)"/);
|
|
305
|
+
const text = end < 0 ? rest : rest.slice(0, end + 2);
|
|
306
|
+
for (const part of text.split(/(\$\{[^}]*\}|\$[A-Za-z_]\w*)/)) {
|
|
307
|
+
const braced = /^\$\{([^}]*)\}$/.exec(part);
|
|
308
|
+
if (braced) {
|
|
309
|
+
push("${", "string");
|
|
310
|
+
push(braced[1] ?? "", "plain");
|
|
311
|
+
push("}", "string");
|
|
312
|
+
} else push(part, /^\$[A-Za-z_]/.test(part) ? "plain" : "string");
|
|
313
|
+
}
|
|
314
|
+
index += text.length;
|
|
315
|
+
command = false;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (rest.startsWith("'")) {
|
|
319
|
+
const end = rest.indexOf("'", 1);
|
|
320
|
+
const text = end < 0 ? rest : rest.slice(0, end + 1);
|
|
321
|
+
push(text, "string");
|
|
322
|
+
index += text.length;
|
|
323
|
+
command = false;
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const operator = /^(?:\d?>>?&?\d?|<|\|\||\||&&|;|&|\$\(|\(|\)|`)/.exec(rest);
|
|
327
|
+
if (operator) {
|
|
328
|
+
const text = operator[0];
|
|
329
|
+
// Shiki: `|`, `||` and redirects in the keyword colour, `&&` and `;` plain.
|
|
330
|
+
push(text, text === "&&" || text === ";" || text === "$(" || text === "(" || text === ")" || text === "`" ? "plain" : "keyword");
|
|
331
|
+
index += text.length;
|
|
332
|
+
command = text !== ")" && !/^\d?>/.test(text) && text !== "<";
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
const variable = /^\$(?:\{[^}]*\}|[A-Za-z_]\w*|[0-9@#?$!*-])/.exec(rest);
|
|
336
|
+
if (variable) {
|
|
337
|
+
push(variable[0], "plain");
|
|
338
|
+
index += variable[0].length;
|
|
339
|
+
command = false;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const word = /^[^\s|&;<>()$"'`]+/.exec(rest)?.[0] ?? rest.charAt(0);
|
|
343
|
+
index += word.length;
|
|
344
|
+
const assignment = /^([A-Za-z_]\w*)=(.*)$/.exec(word);
|
|
345
|
+
if (SHELL_KEYWORDS.has(word) && (command || word === "in" && forState === "in")) {
|
|
346
|
+
push(word, "keyword");
|
|
347
|
+
command = SHELL_COMMAND_KEYWORDS.has(word);
|
|
348
|
+
forState = word === "for" ? "name" : "none";
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
// A test's brackets are plain, and what follows them is not a command.
|
|
352
|
+
if (word === "[" || word === "[[" || word === "]" || word === "]]") {
|
|
353
|
+
push(word, "plain");
|
|
354
|
+
command = false;
|
|
355
|
+
inTest = word.startsWith("[");
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (inTest) {
|
|
359
|
+
push(word, /^-[a-z]+$/.test(word) ? "keyword" : "plain");
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (assignment && (command || runs.at(-2)?.content === "export")) {
|
|
363
|
+
push(assignment[1] ?? "", "plain");
|
|
364
|
+
push("=", "keyword");
|
|
365
|
+
push(assignment[2] ?? "", "plain");
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (forState === "name") {
|
|
369
|
+
push(word, "plain");
|
|
370
|
+
forState = "in";
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (command) {
|
|
374
|
+
push(word, SHELL_BUILTINS.has(word) ? "constant" : "function");
|
|
375
|
+
command = false;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
push(word, /^-/.test(word) || /^\d+$/.test(word) ? "constant" : "string");
|
|
379
|
+
}
|
|
380
|
+
return runs;
|
|
381
|
+
});
|
|
382
|
+
}
|