@stacksjs/stx 0.2.118 → 0.2.120
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/builtins/attrs.d.ts +16 -0
- package/dist/builtins/attrs.js +16 -0
- package/dist/builtins/safe-image.js +2 -9
- package/dist/builtins/stx-image.js +2 -9
- package/dist/builtins/stx-link.js +2 -9
- package/dist/cli.js +1869 -824
- package/dist/client-script-bundler.d.ts +0 -11
- package/dist/client-script-bundler.js +26 -4
- package/dist/client-script.d.ts +24 -0
- package/dist/client-script.js +59 -5
- package/dist/component-registry.d.ts +1 -0
- package/dist/component-renderer.js +13 -5
- package/dist/composables/index.d.ts +1 -0
- package/dist/composables/index.js +1 -0
- package/dist/composables/use-scroll-lock.d.ts +13 -0
- package/dist/composables/use-scroll-lock.js +55 -0
- package/dist/composables/use-timer.d.ts +1 -1
- package/dist/composables/use-timer.js +13 -3
- package/dist/composition-api.d.ts +1 -1
- package/dist/composition-api.js +13 -4
- package/dist/craft-bridge.d.ts +80 -1
- package/dist/craft-bridge.js +80 -1
- package/dist/craft-components.d.ts +77 -3
- package/dist/craft-components.js +138 -9
- package/dist/craft.js +481 -73
- package/dist/dev-server/crosswind.d.ts +15 -1
- package/dist/dev-server/crosswind.js +12 -3
- package/dist/dev-server/serve-app.js +3 -3
- package/dist/document-shell.d.ts +17 -0
- package/dist/document-shell.js +72 -2
- package/dist/expressions.js +14 -7
- package/dist/head.d.ts +8 -0
- package/dist/head.js +11 -8
- package/dist/html-masking.d.ts +12 -0
- package/dist/html-masking.js +50 -15
- package/dist/index.d.ts +9 -1
- package/dist/index.js +9 -1
- package/dist/process.js +11 -3
- package/dist/pwa.js +1172 -431
- package/dist/runtime-globals.d.ts +7 -4
- package/dist/runtime-globals.js +10 -2
- package/dist/runtime-injection.js +15 -1
- package/dist/signal-processing.js +19 -28
- package/dist/signals-api.d.ts +2 -0
- package/dist/signals-api.js +9 -0
- package/dist/signals.js +802 -62
- package/dist/ssg.js +1161 -420
- package/dist/state-management.d.ts +1 -1
- package/dist/state-management.js +1 -1
- package/dist/strict-lint.d.ts +1 -1
- package/dist/strict-lint.js +1 -1
- package/dist/stx.d.ts +6 -0
- package/dist/style-scoping.js +10 -0
- package/dist/utils.js +5 -3
- package/dist/variable-extractor.js +14 -22
- package/dist/vue-template.js +3 -2
- package/dist/x-element.d.ts +2 -2
- package/dist/x-element.js +9 -8
- package/package.json +5 -5
- package/stx.d.ts +6 -0
|
@@ -9,17 +9,6 @@
|
|
|
9
9
|
* - `import { ... } from '@composables'` (handled by transformStoreImports)
|
|
10
10
|
*/
|
|
11
11
|
export declare function hasUserImports(code: string): boolean;
|
|
12
|
-
/**
|
|
13
|
-
* Bundle a client script using Bun.build.
|
|
14
|
-
*
|
|
15
|
-
* Resolves local imports (@/, ./), npm packages, and tree-shakes unused exports.
|
|
16
|
-
* stx runtime, @stores, and @composables are marked as external.
|
|
17
|
-
*
|
|
18
|
-
* @param code - The script content (may contain import statements)
|
|
19
|
-
* @param filePath - The .stx template file path (for relative import resolution)
|
|
20
|
-
* @param options - Bundling options
|
|
21
|
-
* @returns Bundled code with all imports resolved and inlined
|
|
22
|
-
*/
|
|
23
12
|
export declare function bundleClientScript(code: string, filePath: string, options?: {
|
|
24
13
|
projectRoot?: string
|
|
25
14
|
minify?: boolean
|
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import { getPublicEnvDefine } from "./public-env.js";
|
|
4
4
|
import { stateDir } from "./state-dir.js";
|
|
5
|
-
const BUNDLE_CACHE_VERSION =
|
|
5
|
+
const BUNDLE_CACHE_VERSION = 5, BUNDLE_CACHE_METADATA_VERSION = 1, EXTERNAL_PATTERNS = [
|
|
6
6
|
/^stx$/,
|
|
7
7
|
/^@stacksjs\/stx$/,
|
|
8
8
|
/^@stores$/,
|
|
@@ -101,6 +101,7 @@ function createBundlePlugin(projectRoot, templateDir, tmpEntry, inputFiles) {
|
|
|
101
101
|
}
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
|
+
const inFlightBundles = new Map;
|
|
104
105
|
export async function bundleClientScript(code, filePath, options = {}) {
|
|
105
106
|
const projectRoot = options.projectRoot || process.cwd(), minify = options.minify ?? !1, cacheDir = options.cacheDir || stateDir(projectRoot, "bundle-cache"), hasher = new Bun.CryptoHasher("md5");
|
|
106
107
|
hasher.update(`${BUNDLE_CACHE_VERSION}\x00${code}\x00${filePath}`);
|
|
@@ -111,7 +112,9 @@ export async function bundleClientScript(code, filePath, options = {}) {
|
|
|
111
112
|
if (await depsFile.exists())
|
|
112
113
|
try {
|
|
113
114
|
const stored = JSON.parse(await depsFile.text());
|
|
114
|
-
|
|
115
|
+
if (stored.metadataVersion !== BUNDLE_CACHE_METADATA_VERSION || !Array.isArray(stored.files))
|
|
116
|
+
depsValid = !1;
|
|
117
|
+
for (const dep of Array.isArray(stored.files) ? stored.files : [])
|
|
115
118
|
try {
|
|
116
119
|
if (fs.statSync(dep.path).mtimeMs !== dep.mtimeMs) {
|
|
117
120
|
depsValid = !1;
|
|
@@ -130,7 +133,19 @@ export async function bundleClientScript(code, filePath, options = {}) {
|
|
|
130
133
|
}
|
|
131
134
|
console.log("[stx:bundler] cache invalidated by dep change:", hash);
|
|
132
135
|
}
|
|
133
|
-
const
|
|
136
|
+
const running = inFlightBundles.get(hash);
|
|
137
|
+
if (running)
|
|
138
|
+
return await running;
|
|
139
|
+
const build = buildBundle(code, filePath, { projectRoot, minify, cacheDir, hash, cachePath, depsPath });
|
|
140
|
+
inFlightBundles.set(hash, build);
|
|
141
|
+
try {
|
|
142
|
+
return await build;
|
|
143
|
+
} finally {
|
|
144
|
+
inFlightBundles.delete(hash);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function buildBundle(code, filePath, options) {
|
|
148
|
+
const { projectRoot, minify, cacheDir, hash, cachePath, depsPath } = options, tmpDir = stateDir(projectRoot, "bundle-tmp"), tmpEntry = path.join(tmpDir, `${hash}.ts`), tmpOutDir = path.join(tmpDir, "out", hash);
|
|
134
149
|
fs.mkdirSync(tmpDir, { recursive: !0 });
|
|
135
150
|
fs.mkdirSync(tmpOutDir, { recursive: !0 });
|
|
136
151
|
const templateDir = path.dirname(filePath), declNames = [], declRegex = /^(export\s+)?(?:const|let|var)\s+(?:\{([^}]+)\}|(\w+))/gm, funcRegex = /^(export\s+)?(?:async\s+)?function\s+(\w+)/gm;
|
|
@@ -207,6 +222,10 @@ return { ${returnedBindings} };
|
|
|
207
222
|
})();
|
|
208
223
|
${publicAssignments}`.trim();
|
|
209
224
|
console.log("[stx:bundler] bundled:", hash, "output:", bundled.length, "bytes");
|
|
225
|
+
if (declNames.length > 0 && exposedBindings.length === 0) {
|
|
226
|
+
console.warn(`[stx:bundler] dropped every binding for ${path.basename(filePath)} (${hash}); serving the unbundled source and not caching this result`);
|
|
227
|
+
return code;
|
|
228
|
+
}
|
|
210
229
|
fs.mkdirSync(cacheDir, { recursive: !0 });
|
|
211
230
|
await Bun.write(cachePath, bundled);
|
|
212
231
|
const depsSnapshot = Array.from(inputFiles).map((p) => {
|
|
@@ -216,7 +235,10 @@ ${publicAssignments}`.trim();
|
|
|
216
235
|
return null;
|
|
217
236
|
}
|
|
218
237
|
}).filter((d) => d !== null);
|
|
219
|
-
await Bun.write(depsPath, JSON.stringify({
|
|
238
|
+
await Bun.write(depsPath, JSON.stringify({
|
|
239
|
+
metadataVersion: BUNDLE_CACHE_METADATA_VERSION,
|
|
240
|
+
files: depsSnapshot
|
|
241
|
+
}));
|
|
220
242
|
return bundled;
|
|
221
243
|
} catch (error) {
|
|
222
244
|
const msg = error instanceof Error ? error.message : String(error), bunErrors = error?.errors, detail = Array.isArray(bunErrors) && bunErrors.length > 0 ? `
|
package/dist/client-script.d.ts
CHANGED
|
@@ -33,11 +33,31 @@ export declare function extractAndStripCssImports(code: string, options: { fileP
|
|
|
33
33
|
* below un-layered rules, so vendor styles default to lowest precedence.
|
|
34
34
|
*/
|
|
35
35
|
export declare function renderVendorStyleTags(styles: VendorCssImport[]): string;
|
|
36
|
+
/**
|
|
37
|
+
* Turn bare core browser helper usage into a real module import before the
|
|
38
|
+
* client bundler runs.
|
|
39
|
+
*
|
|
40
|
+
* Inline signal scripts execute synchronously while the document is parsed.
|
|
41
|
+
* Loading `window.StacksBrowser` from a later module script is therefore too
|
|
42
|
+
* late. A real import lets Bun inline and tree-shake the selected browser
|
|
43
|
+
* helpers together with the component, exactly like an explicit user import.
|
|
44
|
+
*/
|
|
45
|
+
export declare function injectBrowserCoreAutoImports(code: string): BrowserCoreAutoImportResult;
|
|
36
46
|
/**
|
|
37
47
|
* Generate destructuring statements for auto-imported symbols
|
|
38
48
|
* These access globals set up by the STX runtime and @stacksjs/browser
|
|
39
49
|
*/
|
|
40
50
|
export declare function generateAutoImportDestructuring(stxImports: string[], browserImports: string[]): string;
|
|
51
|
+
/**
|
|
52
|
+
* Generate the browser-runtime bindings needed by a signal setup wrapper.
|
|
53
|
+
*
|
|
54
|
+
* Signal pages and Vue-like signal components use specialized scoped wrappers
|
|
55
|
+
* instead of {@link processClientScript}. They already pull every STX runtime
|
|
56
|
+
* global from `window.stx`, but still need the selected Stacks browser helpers
|
|
57
|
+
* used by their script. Keeping detection here makes classic scripts, signal
|
|
58
|
+
* pages, and signal components share the same browser auto-import contract.
|
|
59
|
+
*/
|
|
60
|
+
export declare function generateBrowserAutoImportDestructuring(code: string, exclude?: readonly string[]): string;
|
|
41
61
|
/**
|
|
42
62
|
* Build the server → client bridge payload from a template context: the
|
|
43
63
|
* top-level, JSON-serializable values (excluding internal `__`/`$` keys and
|
|
@@ -71,6 +91,10 @@ export declare interface VendorCssImport {
|
|
|
71
91
|
resolvedPath: string
|
|
72
92
|
contents: string
|
|
73
93
|
}
|
|
94
|
+
export declare interface BrowserCoreAutoImportResult {
|
|
95
|
+
code: string
|
|
96
|
+
imports: string[]
|
|
97
|
+
}
|
|
74
98
|
// =============================================================================
|
|
75
99
|
// Types
|
|
76
100
|
// =============================================================================
|
package/dist/client-script.js
CHANGED
|
@@ -162,6 +162,7 @@ const STX_AUTO_IMPORTS = [
|
|
|
162
162
|
"useQuery",
|
|
163
163
|
"useMutation",
|
|
164
164
|
"useOptimistic",
|
|
165
|
+
"useId",
|
|
165
166
|
"useReactiveProp",
|
|
166
167
|
"useLocalStorage",
|
|
167
168
|
"useSessionStorage",
|
|
@@ -185,6 +186,7 @@ const STX_AUTO_IMPORTS = [
|
|
|
185
186
|
"useColorMode",
|
|
186
187
|
"useDark",
|
|
187
188
|
"useMediaQuery",
|
|
189
|
+
"useScrollLock",
|
|
188
190
|
"usePreferredDark",
|
|
189
191
|
"usePreferredLight",
|
|
190
192
|
"usePreferredReducedMotion",
|
|
@@ -230,8 +232,53 @@ const STX_AUTO_IMPORTS = [
|
|
|
230
232
|
"getBrowserConfig",
|
|
231
233
|
"createBrowserDb",
|
|
232
234
|
"createBrowserModel",
|
|
233
|
-
"isBrowser"
|
|
234
|
-
|
|
235
|
+
"isBrowser",
|
|
236
|
+
"debounce",
|
|
237
|
+
"throttle",
|
|
238
|
+
"retry",
|
|
239
|
+
"sleep",
|
|
240
|
+
"wait",
|
|
241
|
+
"delay",
|
|
242
|
+
"waitUntil",
|
|
243
|
+
"waitWhile",
|
|
244
|
+
"lazy",
|
|
245
|
+
"clamp",
|
|
246
|
+
"rand",
|
|
247
|
+
"readableSize",
|
|
248
|
+
"useDocumentVisibility",
|
|
249
|
+
"useIntervalFn"
|
|
250
|
+
];
|
|
251
|
+
export function injectBrowserCoreAutoImports(code) {
|
|
252
|
+
const explicitlyImported = new Set;
|
|
253
|
+
for (const match of code.matchAll(/^\s*import\s+(?:type\s+)?\{([^}]*)\}\s+from\s+['"][^'"]+['"]\s*;?\s*$/gm))
|
|
254
|
+
for (const specifier of match[1].split(",")) {
|
|
255
|
+
const localName = specifier.trim().split(/\s+as\s+/).pop()?.replace(/^type\s+/, "").trim();
|
|
256
|
+
if (localName)
|
|
257
|
+
explicitlyImported.add(localName);
|
|
258
|
+
}
|
|
259
|
+
const locallyDeclared = new Set;
|
|
260
|
+
for (const match of code.matchAll(/\b(?:const|let|var|function|class)\s+([A-Za-z_$][\w$]*)/g))
|
|
261
|
+
locallyDeclared.add(match[1]);
|
|
262
|
+
for (const match of code.matchAll(/\b(?:const|let|var)\s*\{([^}]*)\}/g))
|
|
263
|
+
for (const binding of match[1].split(",")) {
|
|
264
|
+
const localName = binding.trim().split(/[:=]/).pop()?.trim();
|
|
265
|
+
if (localName)
|
|
266
|
+
locallyDeclared.add(localName);
|
|
267
|
+
}
|
|
268
|
+
const imports = BROWSER_CORE_IMPORTS.filter((symbol) => {
|
|
269
|
+
if (explicitlyImported.has(symbol) || locallyDeclared.has(symbol))
|
|
270
|
+
return !1;
|
|
271
|
+
return new RegExp(`\\b${symbol}\\b`).test(code);
|
|
272
|
+
});
|
|
273
|
+
if (imports.length === 0)
|
|
274
|
+
return { code, imports };
|
|
275
|
+
return {
|
|
276
|
+
code: `import { ${imports.join(", ")} } from '@stacksjs/browser'
|
|
277
|
+
${code}`,
|
|
278
|
+
imports
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
const JS_BUILTINS = new Set([
|
|
235
282
|
"Promise",
|
|
236
283
|
"Object",
|
|
237
284
|
"Array",
|
|
@@ -350,6 +397,10 @@ ${lines.join(`
|
|
|
350
397
|
${generateAutoImportGuard(stxImports, browserImports)}`;
|
|
351
398
|
return "";
|
|
352
399
|
}
|
|
400
|
+
export function generateBrowserAutoImportDestructuring(code, exclude = []) {
|
|
401
|
+
const { browserImports } = transformAutoImports(code), excluded = new Set(exclude);
|
|
402
|
+
return generateAutoImportDestructuring([], browserImports.filter((name) => !excluded.has(name)));
|
|
403
|
+
}
|
|
353
404
|
function generateAutoImportGuard(stxImports, browserImports) {
|
|
354
405
|
const entries = [
|
|
355
406
|
...stxImports.map((name) => `['${name}', typeof ${name}, 'stx']`),
|
|
@@ -753,13 +804,16 @@ function extractTemplateReferences(templateHtml) {
|
|
|
753
804
|
return refs;
|
|
754
805
|
}
|
|
755
806
|
export async function processClientScript(scriptContent, options = {}) {
|
|
756
|
-
let code = scriptContent, autoImportCode = "", wasBundled = !1;
|
|
807
|
+
let code = scriptContent, autoImportCode = "", wasBundled = !1, bundledBrowserImports = [];
|
|
757
808
|
const cssExtraction = extractAndStripCssImports(code, {
|
|
758
809
|
filePath: options.filePath,
|
|
759
810
|
projectRoot: options.projectRoot
|
|
760
811
|
});
|
|
761
812
|
code = cssExtraction.code;
|
|
762
|
-
const vendorStyleTags = renderVendorStyleTags(cssExtraction.styles),
|
|
813
|
+
const vendorStyleTags = renderVendorStyleTags(cssExtraction.styles), browserCoreImports = injectBrowserCoreAutoImports(code);
|
|
814
|
+
code = browserCoreImports.code;
|
|
815
|
+
bundledBrowserImports = browserCoreImports.imports;
|
|
816
|
+
const { hasUserImports, bundleClientScript } = await import("./client-script-bundler.js");
|
|
763
817
|
if (hasUserImports(code)) {
|
|
764
818
|
console.log("[stx:bundler] user imports detected, bundling...");
|
|
765
819
|
code = await bundleClientScript(code, options.filePath || "", {
|
|
@@ -771,7 +825,7 @@ export async function processClientScript(scriptContent, options = {}) {
|
|
|
771
825
|
if (options.autoImports !== !1) {
|
|
772
826
|
const autoImportResult = transformAutoImports(code);
|
|
773
827
|
code = autoImportResult.code;
|
|
774
|
-
autoImportCode = generateAutoImportDestructuring(autoImportResult.stxImports, autoImportResult.browserImports);
|
|
828
|
+
autoImportCode = generateAutoImportDestructuring(autoImportResult.stxImports, autoImportResult.browserImports.filter((name) => !bundledBrowserImports.includes(name)));
|
|
775
829
|
}
|
|
776
830
|
autoImportCode = `${generateServerDataBridge(code, options.serverData)}${autoImportCode}`;
|
|
777
831
|
code = transformStoreImports(code);
|
|
@@ -27,6 +27,7 @@ export declare interface RenderContext {
|
|
|
27
27
|
*/
|
|
28
28
|
export declare interface ResolvedProps {
|
|
29
29
|
static: Record<string, string | boolean>
|
|
30
|
+
staticNames: Record<string, string>
|
|
30
31
|
serverDynamic: Record<string, unknown>
|
|
31
32
|
clientReactive: Record<string, string>
|
|
32
33
|
events: Record<string, string>
|
|
@@ -176,6 +176,7 @@ const BOOLEAN_ATTRIBUTE_SENTINEL = "\x00stx-boolean-attribute", htmlTags = new S
|
|
|
176
176
|
function parseComponentProps(rawProps, context, options) {
|
|
177
177
|
const resolved = {
|
|
178
178
|
static: {},
|
|
179
|
+
staticNames: {},
|
|
179
180
|
serverDynamic: {},
|
|
180
181
|
clientReactive: {},
|
|
181
182
|
events: {}
|
|
@@ -199,7 +200,14 @@ function parseComponentProps(rawProps, context, options) {
|
|
|
199
200
|
]), kebabToCamel = (key) => {
|
|
200
201
|
if (!key.includes("-") || key.startsWith("@") || key.startsWith(":") || key.startsWith("x-") || key.startsWith("__"))
|
|
201
202
|
return key;
|
|
203
|
+
if (key.startsWith("aria-") || key.startsWith("data-"))
|
|
204
|
+
return key;
|
|
202
205
|
return key.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
206
|
+
}, setStatic = (attrName, value) => {
|
|
207
|
+
const propName = kebabToCamel(attrName);
|
|
208
|
+
resolved.static[propName] = value;
|
|
209
|
+
if (propName !== attrName)
|
|
210
|
+
resolved.staticNames[propName] = attrName;
|
|
203
211
|
};
|
|
204
212
|
for (const [rawAttrName, attrValue] of Object.entries(rawProps)) {
|
|
205
213
|
const attrName = rawAttrName.startsWith("x-") && !ALPINE_PASSTHROUGH.has(rawAttrName) && !rawAttrName.startsWith("x-bind:") && !rawAttrName.startsWith("x-on:") ? `:${rawAttrName.slice(2)}` : rawAttrName;
|
|
@@ -242,16 +250,16 @@ function parseComponentProps(rawProps, context, options) {
|
|
|
242
250
|
const expression = singleExprMatch[1];
|
|
243
251
|
if (isExpressionSafe(expression)) {
|
|
244
252
|
const evaluated = createSafeFunction(expression, Object.keys(context))(...Object.values(context));
|
|
245
|
-
|
|
253
|
+
setStatic(attrName, evaluated != null ? String(evaluated) : "");
|
|
246
254
|
} else {
|
|
247
255
|
if (options.debug)
|
|
248
256
|
console.error(`Unsafe expression in ${attrName}: ${expression}`);
|
|
249
|
-
|
|
257
|
+
setStatic(attrName, attrValue);
|
|
250
258
|
}
|
|
251
259
|
} catch (error) {
|
|
252
260
|
if (options.debug)
|
|
253
261
|
console.error(`Error evaluating expression for ${attrName}:`, error);
|
|
254
|
-
|
|
262
|
+
setStatic(attrName, attrValue);
|
|
255
263
|
}
|
|
256
264
|
else {
|
|
257
265
|
let result = attrValue;
|
|
@@ -271,11 +279,11 @@ function parseComponentProps(rawProps, context, options) {
|
|
|
271
279
|
if (options.debug)
|
|
272
280
|
console.error(`Error evaluating expression in ${attrName}:`, error);
|
|
273
281
|
}
|
|
274
|
-
|
|
282
|
+
setStatic(attrName, result);
|
|
275
283
|
}
|
|
276
284
|
continue;
|
|
277
285
|
}
|
|
278
|
-
|
|
286
|
+
setStatic(attrName, attrValue === BOOLEAN_ATTRIBUTE_SENTINEL ? "true" : attrValue);
|
|
279
287
|
}
|
|
280
288
|
return resolved;
|
|
281
289
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Signal } from '../signals-api';
|
|
2
|
+
/**
|
|
3
|
+
* Reactively lock scrolling on an element, defaulting to the document body.
|
|
4
|
+
*
|
|
5
|
+
* Multiple locks on the same element are reference counted, so releasing one
|
|
6
|
+
* overlay cannot restore scrolling while another overlay is still open.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useScrollLock(target?: ScrollLockTarget): Signal<boolean>;
|
|
9
|
+
declare type ScrollLockTarget = | HTMLElement
|
|
10
|
+
| null
|
|
11
|
+
| undefined
|
|
12
|
+
| { current?: HTMLElement | null, value?: HTMLElement | null }
|
|
13
|
+
| (() => HTMLElement | null | undefined);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { onDestroy, state } from "../signals-api.js";
|
|
2
|
+
const scrollLockStates = new WeakMap;
|
|
3
|
+
function resolveTarget(target) {
|
|
4
|
+
let value = target;
|
|
5
|
+
if (typeof value === "function")
|
|
6
|
+
value = value();
|
|
7
|
+
if (value && typeof value === "object" && "current" in value)
|
|
8
|
+
value = value.current;
|
|
9
|
+
if (value && typeof value === "object" && !("nodeType" in value) && "value" in value)
|
|
10
|
+
value = value.value;
|
|
11
|
+
return value || document.body || document.documentElement;
|
|
12
|
+
}
|
|
13
|
+
function acquire(element) {
|
|
14
|
+
const existing = scrollLockStates.get(element);
|
|
15
|
+
if (existing) {
|
|
16
|
+
existing.count += 1;
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
scrollLockStates.set(element, { count: 1, overflow: element.style.overflow });
|
|
20
|
+
element.style.overflow = "hidden";
|
|
21
|
+
}
|
|
22
|
+
function release(element) {
|
|
23
|
+
const existing = scrollLockStates.get(element);
|
|
24
|
+
if (!existing)
|
|
25
|
+
return;
|
|
26
|
+
existing.count -= 1;
|
|
27
|
+
if (existing.count > 0)
|
|
28
|
+
return;
|
|
29
|
+
element.style.overflow = existing.overflow;
|
|
30
|
+
scrollLockStates.delete(element);
|
|
31
|
+
}
|
|
32
|
+
export function useScrollLock(target) {
|
|
33
|
+
const locked = state(!1);
|
|
34
|
+
let lockedElement = null;
|
|
35
|
+
const unsubscribe = locked.subscribe((value) => {
|
|
36
|
+
if (value) {
|
|
37
|
+
if (lockedElement)
|
|
38
|
+
return;
|
|
39
|
+
lockedElement = resolveTarget(target);
|
|
40
|
+
if (lockedElement)
|
|
41
|
+
acquire(lockedElement);
|
|
42
|
+
} else if (lockedElement) {
|
|
43
|
+
release(lockedElement);
|
|
44
|
+
lockedElement = null;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
onDestroy(() => {
|
|
48
|
+
unsubscribe();
|
|
49
|
+
if (lockedElement) {
|
|
50
|
+
release(lockedElement);
|
|
51
|
+
lockedElement = null;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return locked;
|
|
55
|
+
}
|
|
@@ -39,7 +39,7 @@ export declare function useThrottle<T extends (...args: any[]) => any>(fn: T, li
|
|
|
39
39
|
* // counter increments every second
|
|
40
40
|
* ```
|
|
41
41
|
*/
|
|
42
|
-
export declare function useInterval(interval?: number, options?: IntervalOptions): IntervalRef;
|
|
42
|
+
export declare function useInterval(interval?: number | ((counter: number) => void), options?: IntervalOptions | number): IntervalRef;
|
|
43
43
|
/**
|
|
44
44
|
* One-shot timeout with start/stop control and auto-cleanup.
|
|
45
45
|
*
|
|
@@ -95,16 +95,26 @@ export function useThrottle(fn, limit = 250) {
|
|
|
95
95
|
return throttled;
|
|
96
96
|
}
|
|
97
97
|
export function useInterval(interval = 1000, options = {}) {
|
|
98
|
+
let callback = null;
|
|
99
|
+
if (typeof interval === "function") {
|
|
100
|
+
callback = interval;
|
|
101
|
+
interval = typeof options === "number" ? options : 1000;
|
|
102
|
+
options = {};
|
|
103
|
+
}
|
|
104
|
+
const delay = interval || 1000, config = typeof options === "number" ? {} : options;
|
|
98
105
|
let count = 0, id = null, running = !1;
|
|
99
|
-
const listeners = new Set
|
|
106
|
+
const listeners = new Set;
|
|
107
|
+
if (callback)
|
|
108
|
+
listeners.add(callback);
|
|
109
|
+
const tick = () => {
|
|
100
110
|
count++;
|
|
101
111
|
listeners.forEach((fn) => fn(count));
|
|
102
112
|
}, resume = () => {
|
|
103
113
|
if (running)
|
|
104
114
|
return;
|
|
105
115
|
running = !0;
|
|
106
|
-
id = setInterval(tick,
|
|
107
|
-
if (
|
|
116
|
+
id = setInterval(tick, delay);
|
|
117
|
+
if (config.immediate)
|
|
108
118
|
tick();
|
|
109
119
|
}, pause = () => {
|
|
110
120
|
if (!running)
|
|
@@ -74,7 +74,7 @@ export declare function setGlobalProvide<T>(key: string | symbol, value: T): voi
|
|
|
74
74
|
* emit('change', 'newValue')
|
|
75
75
|
* ```
|
|
76
76
|
*/
|
|
77
|
-
export declare function defineEmits<T extends Record<string, any[]> = Record<string, any[]>>(
|
|
77
|
+
export declare function defineEmits<T extends Record<string, any[]> = Record<string, any[]>>(events?: string[] | T): EmitFn<Extract<keyof T, string>>;
|
|
78
78
|
/**
|
|
79
79
|
* Expose public properties from a component instance.
|
|
80
80
|
* These properties become accessible via template refs from the parent.
|
package/dist/composition-api.js
CHANGED
|
@@ -55,16 +55,25 @@ const globalProvides = new Map;
|
|
|
55
55
|
export function setGlobalProvide(key, value) {
|
|
56
56
|
globalProvides.set(key, value);
|
|
57
57
|
}
|
|
58
|
-
export function defineEmits(
|
|
59
|
-
const instance = currentInstance,
|
|
58
|
+
export function defineEmits(events) {
|
|
59
|
+
const instance = currentInstance, element = instance?.el, declaredEvents = Array.isArray(events) ? events : events && typeof events === "object" ? Object.keys(events) : [];
|
|
60
|
+
if (element && declaredEvents.length > 0) {
|
|
61
|
+
const emittedEvents = element.__stx_component_emits ||= Object.create(null);
|
|
62
|
+
for (const event of declaredEvents)
|
|
63
|
+
emittedEvents[event] = !0;
|
|
64
|
+
}
|
|
65
|
+
const emit = (event, ...args) => {
|
|
60
66
|
if (typeof document > "u")
|
|
61
67
|
return;
|
|
62
|
-
if (
|
|
63
|
-
|
|
68
|
+
if (element) {
|
|
69
|
+
const emittedEvents = element.__stx_component_emits ||= Object.create(null);
|
|
70
|
+
emittedEvents[event] = !0;
|
|
71
|
+
element.dispatchEvent(new CustomEvent(event, {
|
|
64
72
|
detail: args.length === 1 ? args[0] : args,
|
|
65
73
|
bubbles: !0,
|
|
66
74
|
cancelable: !0
|
|
67
75
|
}));
|
|
76
|
+
}
|
|
68
77
|
};
|
|
69
78
|
if (instance)
|
|
70
79
|
instance.emit = emit;
|
package/dist/craft-bridge.d.ts
CHANGED
|
@@ -186,6 +186,12 @@ else if (data.type === 'event' && data.method) {
|
|
|
186
186
|
});
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
// Live component handles, keyed by id, so an event coming back from the host
|
|
190
|
+
// can be routed to the callbacks registered on the right one. Without this
|
|
191
|
+
// the onSelect/onSpaceChange callbacks were stored and never invoked.
|
|
192
|
+
const sidebars = new Map();
|
|
193
|
+
const spacesSidebars = new Map();
|
|
194
|
+
|
|
189
195
|
function createNativeUIFacade() {
|
|
190
196
|
class Sidebar {
|
|
191
197
|
constructor(id) {
|
|
@@ -205,10 +211,42 @@ else if (data.type === 'event' && data.method) {
|
|
|
205
211
|
return this;
|
|
206
212
|
}
|
|
207
213
|
destroy() {
|
|
214
|
+
sidebars.delete(this.id);
|
|
208
215
|
sendNativeUI('destroyComponent', { id: this.id, type: 'sidebar' });
|
|
209
216
|
}
|
|
210
217
|
}
|
|
211
218
|
|
|
219
|
+
/**
|
|
220
|
+
* An Arc-style sidebar of swipeable spaces.
|
|
221
|
+
*
|
|
222
|
+
* Separate from Sidebar rather than an option on it: a source list selects
|
|
223
|
+
* one row out of many, while a spaces sidebar selects one whole SCENE, and
|
|
224
|
+
* a host that renders the switcher natively (a segmented control in the
|
|
225
|
+
* toolbar, a Touch Bar strip) needs the space list, not the item list.
|
|
226
|
+
*/
|
|
227
|
+
class SpacesSidebar {
|
|
228
|
+
constructor(id) {
|
|
229
|
+
this.id = id;
|
|
230
|
+
this._spaceCallbacks = [];
|
|
231
|
+
}
|
|
232
|
+
setSpaces(spaces) {
|
|
233
|
+
sendNativeUI('setSidebarSpaces', { sidebarId: this.id, spaces });
|
|
234
|
+
return this;
|
|
235
|
+
}
|
|
236
|
+
setActiveSpace(spaceId) {
|
|
237
|
+
sendNativeUI('setActiveSpace', { sidebarId: this.id, spaceId });
|
|
238
|
+
return this;
|
|
239
|
+
}
|
|
240
|
+
onSpaceChange(callback) {
|
|
241
|
+
this._spaceCallbacks.push(callback);
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
244
|
+
destroy() {
|
|
245
|
+
spacesSidebars.delete(this.id);
|
|
246
|
+
sendNativeUI('destroyComponent', { id: this.id, type: 'spacesSidebar' });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
212
250
|
class FileBrowser {
|
|
213
251
|
constructor(id) {
|
|
214
252
|
this.id = id;
|
|
@@ -245,12 +283,33 @@ else if (data.type === 'event' && data.method) {
|
|
|
245
283
|
}
|
|
246
284
|
}
|
|
247
285
|
|
|
286
|
+
// Host -> page. The host names the component in the payload; we look up the
|
|
287
|
+
// handle and fan out to whatever the page registered on it.
|
|
288
|
+
on('sidebar.select', function(params) {
|
|
289
|
+
const sidebar = sidebars.get(params && params.sidebarId);
|
|
290
|
+
if (sidebar) sidebar._selectCallbacks.forEach(fn => fn(params.itemId));
|
|
291
|
+
});
|
|
292
|
+
on('sidebar.spaceChange', function(params) {
|
|
293
|
+
const sidebar = spacesSidebars.get(params && params.sidebarId);
|
|
294
|
+
if (sidebar) sidebar._spaceCallbacks.forEach(fn => fn(params.spaceId));
|
|
295
|
+
});
|
|
296
|
+
|
|
248
297
|
return {
|
|
249
298
|
createSidebar(options) {
|
|
250
299
|
const opts = options || {};
|
|
251
300
|
const id = opts.id || 'sidebar-' + Date.now() + '-' + Math.random().toString(36).slice(2);
|
|
252
301
|
sendNativeUI('createSidebar', Object.assign({}, opts, { id }));
|
|
253
|
-
|
|
302
|
+
const sidebar = new Sidebar(id);
|
|
303
|
+
sidebars.set(id, sidebar);
|
|
304
|
+
return sidebar;
|
|
305
|
+
},
|
|
306
|
+
createSpacesSidebar(options) {
|
|
307
|
+
const opts = options || {};
|
|
308
|
+
const id = opts.id || 'spaces-' + Date.now() + '-' + Math.random().toString(36).slice(2);
|
|
309
|
+
sendNativeUI('createSpacesSidebar', Object.assign({}, opts, { id }));
|
|
310
|
+
const sidebar = new SpacesSidebar(id);
|
|
311
|
+
spacesSidebars.set(id, sidebar);
|
|
312
|
+
return sidebar;
|
|
254
313
|
},
|
|
255
314
|
createFileBrowser(options) {
|
|
256
315
|
const opts = options || {};
|
|
@@ -399,6 +458,26 @@ else if (data.type === 'event' && data.method) {
|
|
|
399
458
|
platform: () => request('process.platform'),
|
|
400
459
|
},
|
|
401
460
|
|
|
461
|
+
// Gesture stream
|
|
462
|
+
//
|
|
463
|
+
// A webview's wheel events carry no phase information, so a page can only
|
|
464
|
+
// guess where a trackpad swipe begins and ends — typically with an idle
|
|
465
|
+
// timeout, which mistimes the release and cannot see momentum at all. The
|
|
466
|
+
// host holds the real NSEvent, so it forwards the phases instead:
|
|
467
|
+
//
|
|
468
|
+
// { axis: 'horizontal' | 'vertical',
|
|
469
|
+
// phase: 'begin' | 'change' | 'end',
|
|
470
|
+
// deltaX, deltaY, // points since the last event
|
|
471
|
+
// velocityX, velocityY, // points per second, at 'end'
|
|
472
|
+
// momentum: boolean } // the OS is coasting, not the finger
|
|
473
|
+
//
|
|
474
|
+
// Consumers must stay correct when nothing ever arrives: a host without
|
|
475
|
+
// gesture support simply never emits, so keep the wheel path bound and let
|
|
476
|
+
// a begin phase take over from it.
|
|
477
|
+
gestures: {
|
|
478
|
+
onSwipe: (callback) => on('gesture.swipe', callback),
|
|
479
|
+
},
|
|
480
|
+
|
|
402
481
|
// Native component helpers
|
|
403
482
|
nativeUI,
|
|
404
483
|
components: componentAPI
|