@oss-idle/addon-sdk 0.0.0-stage → 0.6.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/LICENSE +661 -0
- package/README.md +262 -2
- package/bin/cli.mjs +137 -0
- package/bin/scaffold.mjs +157 -0
- package/dist/bridge/beatmaps.d.ts +57 -0
- package/dist/bridge/beatmaps.d.ts.map +1 -0
- package/dist/bridge/beatmaps.js +65 -0
- package/dist/bridge/character.d.ts +91 -0
- package/dist/bridge/character.d.ts.map +1 -0
- package/dist/bridge/character.js +107 -0
- package/dist/bridge/db.d.ts +44 -0
- package/dist/bridge/db.d.ts.map +1 -0
- package/dist/bridge/db.js +83 -0
- package/dist/bridge/playlists.d.ts +55 -0
- package/dist/bridge/playlists.d.ts.map +1 -0
- package/dist/bridge/playlists.js +85 -0
- package/dist/bridge/scores.d.ts +28 -0
- package/dist/bridge/scores.d.ts.map +1 -0
- package/dist/bridge/scores.js +43 -0
- package/dist/build/index.d.ts +73 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/build/index.js +126 -0
- package/dist/build/plugins.d.ts +95 -0
- package/dist/build/plugins.d.ts.map +1 -0
- package/dist/build/plugins.js +240 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +3 -0
- package/dist/react/overlay.d.ts +56 -0
- package/dist/react/overlay.d.ts.map +1 -0
- package/dist/react/overlay.js +63 -0
- package/dist/react/reactAddon.d.ts +37 -0
- package/dist/react/reactAddon.d.ts.map +1 -0
- package/dist/react/reactAddon.js +51 -0
- package/dist/react/useSettings.d.ts +16 -0
- package/dist/react/useSettings.d.ts.map +1 -0
- package/dist/react/useSettings.js +30 -0
- package/dist/runtime/addon.d.ts +94 -0
- package/dist/runtime/addon.d.ts.map +1 -0
- package/dist/runtime/addon.js +129 -0
- package/dist/runtime/buildMeta.d.ts +11 -0
- package/dist/runtime/buildMeta.d.ts.map +1 -0
- package/dist/runtime/buildMeta.js +1 -0
- package/dist/runtime/define.d.ts +26 -0
- package/dist/runtime/define.d.ts.map +1 -0
- package/dist/runtime/define.js +40 -0
- package/dist/runtime/host.d.ts +113 -0
- package/dist/runtime/host.d.ts.map +1 -0
- package/dist/runtime/host.js +8 -0
- package/dist/runtime/input.d.ts +58 -0
- package/dist/runtime/input.d.ts.map +1 -0
- package/dist/runtime/input.js +119 -0
- package/dist/runtime/settings.d.ts +36 -0
- package/dist/runtime/settings.d.ts.map +1 -0
- package/dist/runtime/settings.js +74 -0
- package/package.json +83 -4
- package/templates/common/README.md +50 -0
- package/templates/common/addon.config.json +7 -0
- package/templates/common/eslint.config.js +54 -0
- package/templates/common/gitignore +3 -0
- package/templates/common/src/env.d.ts +17 -0
- package/templates/common/tsconfig.json +14 -0
- package/templates/plain/src/index.ts +32 -0
- package/templates/react/src/Addon.tsx +44 -0
- package/templates/react/src/Panel.tsx +41 -0
- package/templates/react/src/index.ts +12 -0
- package/templates/react/src/styles.css +61 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The add-on bundler.
|
|
3
|
+
*
|
|
4
|
+
* oss!idle stores an add-on as one `source` string and loads it with
|
|
5
|
+
* `import(URL.createObjectURL(new Blob([source])))`. A Blob module has no base
|
|
6
|
+
* URL and no import map, so **nothing** resolves at load time: React, the
|
|
7
|
+
* game's simulation, workers and CSS must all already be inside that one file,
|
|
8
|
+
* and it must be an ES module exporting `mount` and `unmount`.
|
|
9
|
+
*
|
|
10
|
+
* That is what this produces.
|
|
11
|
+
*
|
|
12
|
+
* Output is **not minified by default**. Published add-ons are read by a human
|
|
13
|
+
* moderator before they go live, and a single minified line is a poor thing to
|
|
14
|
+
* ask anyone to review.
|
|
15
|
+
*/
|
|
16
|
+
import { build as esbuild } from 'esbuild';
|
|
17
|
+
import { createRequire } from 'node:module';
|
|
18
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
19
|
+
import { dirname, resolve } from 'node:path';
|
|
20
|
+
import { assetQueryPlugin, cssTextPlugin, dedupePlugin, hostReactPlugin, inlineWorkerPlugin, sharedSourcePlugin, stubModulePlugin, tsExtensionPlugin, } from './plugins.js';
|
|
21
|
+
/**
|
|
22
|
+
* The individual esbuild plugins, for a build that needs to assemble its own
|
|
23
|
+
* pipeline rather than call {@link bundleAddon} - compiling a slice of an add-on
|
|
24
|
+
* for a test, say, or bundling a worker on its own.
|
|
25
|
+
*/
|
|
26
|
+
export { assetQueryPlugin, cssTextPlugin, dedupePlugin, hostReactPlugin, inlineWorkerPlugin, sharedSourcePlugin, stubModulePlugin, tsExtensionPlugin, } from './plugins.js';
|
|
27
|
+
const sharedSrcOf = (gameRoot) => resolve(gameRoot, 'packages/shared/src');
|
|
28
|
+
/** esbuild settings shared by the add-on bundle and any inlined worker. */
|
|
29
|
+
const baseOptions = (gameRoot, minify, stubModules = {}, dedupe = [], dedupeFrom = process.cwd(), plugins = []) => ({
|
|
30
|
+
bundle: true,
|
|
31
|
+
format: 'esm',
|
|
32
|
+
platform: 'browser',
|
|
33
|
+
target: 'es2022',
|
|
34
|
+
// The add-on runs inside the shipped client, which is a production React build.
|
|
35
|
+
define: { 'process.env.NODE_ENV': '"production"' },
|
|
36
|
+
jsx: 'automatic',
|
|
37
|
+
legalComments: 'inline',
|
|
38
|
+
// A Blob-loaded module cannot fetch a sibling asset, so a WebAssembly
|
|
39
|
+
// binary has to travel inside the bundle. Imported as base64, it is decoded
|
|
40
|
+
// at runtime and handed to the module's init as a BufferSource. Costs about
|
|
41
|
+
// a third more bytes than the binary itself.
|
|
42
|
+
loader: { '.wasm': 'base64' },
|
|
43
|
+
minify,
|
|
44
|
+
plugins: [
|
|
45
|
+
...plugins,
|
|
46
|
+
// Strip asset suffixes before anything tries to resolve them as modules.
|
|
47
|
+
assetQueryPlugin(),
|
|
48
|
+
// Dedupe first: it must see a bare package name before anything else
|
|
49
|
+
// resolves it out of the game's checkout.
|
|
50
|
+
dedupePlugin(dedupe, dedupeFrom),
|
|
51
|
+
...(gameRoot ? [sharedSourcePlugin(sharedSrcOf(gameRoot))] : []),
|
|
52
|
+
tsExtensionPlugin(),
|
|
53
|
+
stubModulePlugin(stubModules),
|
|
54
|
+
cssTextPlugin(),
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
/** Build one worker entry to a self-contained string for inlining. */
|
|
58
|
+
const buildWorkerSource = async (entry, gameRoot, minify, stubModules, dedupe, dedupeFrom, plugins) => {
|
|
59
|
+
const result = await esbuild({
|
|
60
|
+
...baseOptions(gameRoot, minify, stubModules, dedupe, dedupeFrom, plugins),
|
|
61
|
+
entryPoints: [entry],
|
|
62
|
+
write: false,
|
|
63
|
+
// A worker built as ESM would need `type: 'module'` support *and* would
|
|
64
|
+
// keep top-level await; IIFE keeps the inlined string self-contained.
|
|
65
|
+
format: 'iife',
|
|
66
|
+
});
|
|
67
|
+
return result.outputFiles?.[0]?.text ?? '';
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* This SDK's own version, for the banner and the stamped meta - the two places
|
|
71
|
+
* a reader asks what built a bundle they are holding.
|
|
72
|
+
*/
|
|
73
|
+
const sdkVersion = createRequire(import.meta.url)('@oss-idle/addon-sdk/package.json').version;
|
|
74
|
+
/** Bundle an add-on into the single ES module the runtime loads. */
|
|
75
|
+
export const bundleAddon = async (options) => {
|
|
76
|
+
const { entry, outfile, gameRoot, meta, minify = false, sourcemap = false, stubModules = {}, dedupe = [], dedupeFrom = dirname(resolve(entry)), plugins = [], sharedReact = false, } = options;
|
|
77
|
+
// The SDK's React modules are imported from wherever npm put the SDK, so
|
|
78
|
+
// `react` resolves from there unless it is pointed back at the add-on that
|
|
79
|
+
// installed it - and two Reacts in one bundle break hooks outright. With the
|
|
80
|
+
// game's React there is none to bundle.
|
|
81
|
+
const names = [...new Set([...sharedReact ? [] : ['react', 'react-dom'], ...dedupe])];
|
|
82
|
+
const ownPlugins = sharedReact ? [hostReactPlugin(dedupeFrom), ...plugins] : plugins;
|
|
83
|
+
const base = baseOptions(gameRoot, minify, stubModules, names, dedupeFrom, ownPlugins);
|
|
84
|
+
const result = await esbuild({
|
|
85
|
+
...base,
|
|
86
|
+
// Read back at runtime by `buildMeta()`.
|
|
87
|
+
define: { ...base.define, __OSS_IDLE_ADDON__: JSON.stringify({ ...meta, sdkVersion }) },
|
|
88
|
+
entryPoints: [entry],
|
|
89
|
+
write: false,
|
|
90
|
+
sourcemap: sourcemap ? 'inline' : false,
|
|
91
|
+
plugins: [
|
|
92
|
+
inlineWorkerPlugin(workerEntry => buildWorkerSource(workerEntry, gameRoot, minify, stubModules, dedupe, dedupeFrom, plugins)),
|
|
93
|
+
...(base.plugins ?? []),
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
const code = result.outputFiles?.[0]?.text ?? '';
|
|
97
|
+
// The trailing blank line is not optional: without it the last banner comment
|
|
98
|
+
// would swallow the first line of generated code.
|
|
99
|
+
const banner = meta
|
|
100
|
+
? [
|
|
101
|
+
'// ' + [meta.name, meta.version && `v${meta.version}`].filter(Boolean).join(' '),
|
|
102
|
+
meta.gameVersion && `// built against oss!idle ${meta.gameVersion}`,
|
|
103
|
+
meta.license && `// ${meta.license}`,
|
|
104
|
+
`// Bundled with @oss-idle/addon-sdk v${sdkVersion}. Paste this file into the add-on editor.`,
|
|
105
|
+
].filter(Boolean).join('\n') + '\n\n'
|
|
106
|
+
: '';
|
|
107
|
+
const source = banner + code;
|
|
108
|
+
await mkdir(dirname(resolve(outfile)), { recursive: true });
|
|
109
|
+
await writeFile(outfile, source, 'utf8');
|
|
110
|
+
await assertAddonShape(outfile, source);
|
|
111
|
+
return { outfile, bytes: Buffer.byteLength(source, 'utf8') };
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Fail the build if the output would not satisfy the runtime contract.
|
|
115
|
+
*
|
|
116
|
+
* A bundle missing `mount` loads without error and simply does nothing, which
|
|
117
|
+
* is a miserable thing to debug from inside the game - much better to catch it
|
|
118
|
+
* here.
|
|
119
|
+
*/
|
|
120
|
+
const assertAddonShape = async (outfile, source) => {
|
|
121
|
+
const code = source || await readFile(outfile, 'utf8');
|
|
122
|
+
if (!/export\s*\{[^}]*\bmount\b/.test(code) && !/export\s+(const|function)\s+mount\b/.test(code)) {
|
|
123
|
+
throw new Error(`${outfile} does not export "mount" - the add-on runtime would load it and call nothing. ` +
|
|
124
|
+
'Export it from your entry, e.g. `export const { mount, unmount } = defineAddon(...)`.');
|
|
125
|
+
}
|
|
126
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Plugin } from 'esbuild';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve `@oss-idle/shared/*` against a checkout of the game.
|
|
4
|
+
*
|
|
5
|
+
* The package is `private: true` and never published, so there is nothing to
|
|
6
|
+
* install - the add-on compiles the game's TypeScript sources into its own
|
|
7
|
+
* bundle instead. Its `exports` map points at `dist/`, which a plain checkout
|
|
8
|
+
* has not built, so this resolves to `src/` directly.
|
|
9
|
+
*
|
|
10
|
+
* The sources are written for NodeNext, where a relative import of a TypeScript
|
|
11
|
+
* file spells its *output* extension (`'../skills.js'` for `skills.ts`). esbuild
|
|
12
|
+
* takes that literally and would fail to find the file, so both this plugin and
|
|
13
|
+
* {@link tsExtensionPlugin} map `.js` back onto the `.ts` that produced it.
|
|
14
|
+
*/
|
|
15
|
+
export declare const sharedSourcePlugin: (sharedSrc: string) => Plugin;
|
|
16
|
+
/**
|
|
17
|
+
* Map a relative `./x.js` import onto the `./x.ts` beside it.
|
|
18
|
+
*
|
|
19
|
+
* Only applies when the `.js` file does not exist and a `.ts` does, so a
|
|
20
|
+
* genuine JavaScript dependency is never hijacked.
|
|
21
|
+
*/
|
|
22
|
+
export declare const tsExtensionPlugin: () => Plugin;
|
|
23
|
+
/**
|
|
24
|
+
* `import worker from 'worker:./sim.worker'` - build that entry as its own
|
|
25
|
+
* bundle and inline it as a source string, plus a factory that spawns it from a
|
|
26
|
+
* Blob URL.
|
|
27
|
+
*
|
|
28
|
+
* An add-on is itself loaded from a Blob, so it has no URL to resolve a worker
|
|
29
|
+
* file against; the worker has to travel inside the same file. Workers matter
|
|
30
|
+
* here because the game is a rhythm game sharing this thread - a simulation
|
|
31
|
+
* sweep run on the main thread would stutter gameplay.
|
|
32
|
+
*/
|
|
33
|
+
export declare const inlineWorkerPlugin: (buildWorker: (entry: string) => Promise<string>) => Plugin;
|
|
34
|
+
/** Load `.css` imports as strings, for injection into the overlay's shadow root. */
|
|
35
|
+
export declare const cssTextPlugin: () => Plugin;
|
|
36
|
+
/**
|
|
37
|
+
* Replace a module the add-on never actually calls.
|
|
38
|
+
*
|
|
39
|
+
* The game's modules are written for an app that ships everything, so a small
|
|
40
|
+
* helper can sit in a file that also pulls in something large - importing
|
|
41
|
+
* `hasUnlock` from `shared/rebirth` drags in the whole of zod, half a megabyte,
|
|
42
|
+
* for three lines of logic that never touch it. esbuild cannot drop that on its
|
|
43
|
+
* own: building a zod schema at module scope is a side effect, so the import is
|
|
44
|
+
* kept even though nothing uses its exports.
|
|
45
|
+
*
|
|
46
|
+
* Stubbing lets the add-on keep importing the game's real helper - so the logic
|
|
47
|
+
* stays the game's, not a copy - while the unused dependency is left out.
|
|
48
|
+
*
|
|
49
|
+
* The stub **throws** if anything ever reaches it, so a wrong guess about what
|
|
50
|
+
* is unused fails loudly at the call instead of silently misbehaving.
|
|
51
|
+
*
|
|
52
|
+
* `stubs` maps a path suffix (e.g. `shared/src/onboarding.ts`) to the names to
|
|
53
|
+
* export from the replacement.
|
|
54
|
+
*/
|
|
55
|
+
export declare const stubModulePlugin: (stubs: Record<string, readonly string[]>) => Plugin;
|
|
56
|
+
/**
|
|
57
|
+
* Force a package to resolve to one copy.
|
|
58
|
+
*
|
|
59
|
+
* An add-on bundles its own sources *and* the game's, which live in a different
|
|
60
|
+
* checkout with its own `node_modules`. esbuild resolves each import from the
|
|
61
|
+
* importing file, so a package both sides use - `osu-classes`, say - gets
|
|
62
|
+
* bundled **twice**: the game's sim ends up holding different class objects from
|
|
63
|
+
* the ones the add-on's decoder produces.
|
|
64
|
+
*
|
|
65
|
+
* That is not just wasted bytes. Prototype patches apply to one copy while the
|
|
66
|
+
* other is the one actually used (the game's control-point decode patch fails
|
|
67
|
+
* exactly this way), and `instanceof` across the boundary silently returns
|
|
68
|
+
* false.
|
|
69
|
+
*
|
|
70
|
+
* Resolving these names from a single directory collapses them to one copy.
|
|
71
|
+
* The versions must be compatible, which is why the add-on pins the same ones
|
|
72
|
+
* the game does.
|
|
73
|
+
*/
|
|
74
|
+
export declare const dedupePlugin: (names: readonly string[], fromDir: string) => Plugin;
|
|
75
|
+
/**
|
|
76
|
+
* Explicit asset imports: `import bytes from './thing.wasm?base64'`.
|
|
77
|
+
*
|
|
78
|
+
* The suffix does two jobs. It says at the import site how the file arrives -
|
|
79
|
+
* mirroring the `?url` convention the client uses under Vite - and it keeps
|
|
80
|
+
* TypeScript off a shipped `.wasm.d.ts`, which would otherwise win over the
|
|
81
|
+
* add-on's own `declare module` and report the import as having no default
|
|
82
|
+
* export.
|
|
83
|
+
*
|
|
84
|
+
* Resolution strips the suffix, so esbuild's loader for the real extension
|
|
85
|
+
* (`.wasm` -> base64) applies as normal.
|
|
86
|
+
*/
|
|
87
|
+
export declare const assetQueryPlugin: () => Plugin;
|
|
88
|
+
/**
|
|
89
|
+
* React from the game instead of a bundled copy: each React import becomes a small
|
|
90
|
+
* module reading the game's own React off `globalThis.ossIdle.react`, which the game
|
|
91
|
+
* sets before it loads any add-on. Its named exports are the installed React's, read
|
|
92
|
+
* here at build time, so `import { useState } from 'react'` keeps working.
|
|
93
|
+
*/
|
|
94
|
+
export declare const hostReactPlugin: (fromDir: string) => Plugin;
|
|
95
|
+
//# sourceMappingURL=plugins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../../src/build/plugins.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,GAAI,WAAW,MAAM,KAAG,MAgBrD,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,QAAO,MAanC,CAAC;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,GAAI,aAAa,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,KAAG,MA0BnF,CAAC;AAEH,oFAAoF;AACpF,eAAO,MAAM,aAAa,QAAO,MAQ/B,CAAC;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,KAAG,MAsB1E,CAAC;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,SAAS,MAAM,EAAE,EAAE,SAAS,MAAM,KAAG,MAmBvE,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,QAAO,MAgBlC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,MAAM,KAAG,MAmBhD,CAAC"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { dirname, resolve } from 'node:path';
|
|
5
|
+
/**
|
|
6
|
+
* Resolve `@oss-idle/shared/*` against a checkout of the game.
|
|
7
|
+
*
|
|
8
|
+
* The package is `private: true` and never published, so there is nothing to
|
|
9
|
+
* install - the add-on compiles the game's TypeScript sources into its own
|
|
10
|
+
* bundle instead. Its `exports` map points at `dist/`, which a plain checkout
|
|
11
|
+
* has not built, so this resolves to `src/` directly.
|
|
12
|
+
*
|
|
13
|
+
* The sources are written for NodeNext, where a relative import of a TypeScript
|
|
14
|
+
* file spells its *output* extension (`'../skills.js'` for `skills.ts`). esbuild
|
|
15
|
+
* takes that literally and would fail to find the file, so both this plugin and
|
|
16
|
+
* {@link tsExtensionPlugin} map `.js` back onto the `.ts` that produced it.
|
|
17
|
+
*/
|
|
18
|
+
export const sharedSourcePlugin = (sharedSrc) => ({
|
|
19
|
+
name: 'oss-idle-shared-source',
|
|
20
|
+
setup(build) {
|
|
21
|
+
build.onResolve({ filter: /^@oss-idle\/shared(\/.*)?$/ }, args => {
|
|
22
|
+
const subpath = args.path.replace(/^@oss-idle\/shared\/?/, '') || 'index';
|
|
23
|
+
const base = resolve(sharedSrc, subpath.replace(/\.js$/, ''));
|
|
24
|
+
for (const candidate of [`${base}.ts`, `${base}.tsx`, resolve(base, 'index.ts')]) {
|
|
25
|
+
if (existsSync(candidate))
|
|
26
|
+
return { path: candidate };
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
errors: [{
|
|
30
|
+
text: `Cannot resolve "${args.path}" in the oss!idle checkout (looked under ${sharedSrc})`,
|
|
31
|
+
}],
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Map a relative `./x.js` import onto the `./x.ts` beside it.
|
|
38
|
+
*
|
|
39
|
+
* Only applies when the `.js` file does not exist and a `.ts` does, so a
|
|
40
|
+
* genuine JavaScript dependency is never hijacked.
|
|
41
|
+
*/
|
|
42
|
+
export const tsExtensionPlugin = () => ({
|
|
43
|
+
name: 'ts-extension',
|
|
44
|
+
setup(build) {
|
|
45
|
+
build.onResolve({ filter: /^\.{1,2}\/.*\.js$/ }, args => {
|
|
46
|
+
if (!args.importer.match(/\.tsx?$/))
|
|
47
|
+
return undefined;
|
|
48
|
+
const js = resolve(dirname(args.importer), args.path);
|
|
49
|
+
if (existsSync(js))
|
|
50
|
+
return undefined;
|
|
51
|
+
for (const candidate of [js.replace(/\.js$/, '.ts'), js.replace(/\.js$/, '.tsx')]) {
|
|
52
|
+
if (existsSync(candidate))
|
|
53
|
+
return { path: candidate };
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
/**
|
|
60
|
+
* `import worker from 'worker:./sim.worker'` - build that entry as its own
|
|
61
|
+
* bundle and inline it as a source string, plus a factory that spawns it from a
|
|
62
|
+
* Blob URL.
|
|
63
|
+
*
|
|
64
|
+
* An add-on is itself loaded from a Blob, so it has no URL to resolve a worker
|
|
65
|
+
* file against; the worker has to travel inside the same file. Workers matter
|
|
66
|
+
* here because the game is a rhythm game sharing this thread - a simulation
|
|
67
|
+
* sweep run on the main thread would stutter gameplay.
|
|
68
|
+
*/
|
|
69
|
+
export const inlineWorkerPlugin = (buildWorker) => ({
|
|
70
|
+
name: 'inline-worker',
|
|
71
|
+
setup(build) {
|
|
72
|
+
build.onResolve({ filter: /^worker:/ }, args => {
|
|
73
|
+
const target = args.path.slice('worker:'.length);
|
|
74
|
+
const base = resolve(dirname(args.importer), target);
|
|
75
|
+
for (const candidate of [base, `${base}.ts`, `${base}.js`]) {
|
|
76
|
+
if (existsSync(candidate))
|
|
77
|
+
return { path: candidate, namespace: 'inline-worker' };
|
|
78
|
+
}
|
|
79
|
+
return { errors: [{ text: `Worker entry not found: ${args.path}` }] };
|
|
80
|
+
});
|
|
81
|
+
build.onLoad({ filter: /.*/, namespace: 'inline-worker' }, async (args) => ({
|
|
82
|
+
contents: `
|
|
83
|
+
const source = ${JSON.stringify(await buildWorker(args.path))};
|
|
84
|
+
let url;
|
|
85
|
+
/** Spawn the inlined worker. The Blob URL is created once and reused. */
|
|
86
|
+
export default function createWorker() {
|
|
87
|
+
url ??= URL.createObjectURL(new Blob([source], { type: 'text/javascript' }));
|
|
88
|
+
return new Worker(url, { type: 'module' });
|
|
89
|
+
}
|
|
90
|
+
`,
|
|
91
|
+
loader: 'js',
|
|
92
|
+
resolveDir: dirname(args.path),
|
|
93
|
+
}));
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
/** Load `.css` imports as strings, for injection into the overlay's shadow root. */
|
|
97
|
+
export const cssTextPlugin = () => ({
|
|
98
|
+
name: 'css-text',
|
|
99
|
+
setup(build) {
|
|
100
|
+
build.onLoad({ filter: /\.css$/ }, async (args) => ({
|
|
101
|
+
contents: `export default ${JSON.stringify(await readFile(args.path, 'utf8'))};`,
|
|
102
|
+
loader: 'js',
|
|
103
|
+
}));
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
/**
|
|
107
|
+
* Replace a module the add-on never actually calls.
|
|
108
|
+
*
|
|
109
|
+
* The game's modules are written for an app that ships everything, so a small
|
|
110
|
+
* helper can sit in a file that also pulls in something large - importing
|
|
111
|
+
* `hasUnlock` from `shared/rebirth` drags in the whole of zod, half a megabyte,
|
|
112
|
+
* for three lines of logic that never touch it. esbuild cannot drop that on its
|
|
113
|
+
* own: building a zod schema at module scope is a side effect, so the import is
|
|
114
|
+
* kept even though nothing uses its exports.
|
|
115
|
+
*
|
|
116
|
+
* Stubbing lets the add-on keep importing the game's real helper - so the logic
|
|
117
|
+
* stays the game's, not a copy - while the unused dependency is left out.
|
|
118
|
+
*
|
|
119
|
+
* The stub **throws** if anything ever reaches it, so a wrong guess about what
|
|
120
|
+
* is unused fails loudly at the call instead of silently misbehaving.
|
|
121
|
+
*
|
|
122
|
+
* `stubs` maps a path suffix (e.g. `shared/src/onboarding.ts`) to the names to
|
|
123
|
+
* export from the replacement.
|
|
124
|
+
*/
|
|
125
|
+
export const stubModulePlugin = (stubs) => ({
|
|
126
|
+
name: 'stub-module',
|
|
127
|
+
setup(build) {
|
|
128
|
+
const suffixes = Object.keys(stubs);
|
|
129
|
+
if (!suffixes.length)
|
|
130
|
+
return;
|
|
131
|
+
build.onLoad({ filter: /\.tsx?$/ }, args => {
|
|
132
|
+
const normalized = args.path.split('\\').join('/');
|
|
133
|
+
const match = suffixes.find(suffix => normalized.endsWith(suffix));
|
|
134
|
+
if (!match)
|
|
135
|
+
return undefined;
|
|
136
|
+
const names = stubs[match] ?? [];
|
|
137
|
+
const contents = names.map(name => `export const ${name} = new Proxy(function () {}, {\n` +
|
|
138
|
+
` get() { throw new Error('${name} was stubbed out of this add-on bundle but is being used'); },\n` +
|
|
139
|
+
` apply() { throw new Error('${name} was stubbed out of this add-on bundle but is being called'); },\n` +
|
|
140
|
+
'});').join('\n');
|
|
141
|
+
return { contents, loader: 'js' };
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
/**
|
|
146
|
+
* Force a package to resolve to one copy.
|
|
147
|
+
*
|
|
148
|
+
* An add-on bundles its own sources *and* the game's, which live in a different
|
|
149
|
+
* checkout with its own `node_modules`. esbuild resolves each import from the
|
|
150
|
+
* importing file, so a package both sides use - `osu-classes`, say - gets
|
|
151
|
+
* bundled **twice**: the game's sim ends up holding different class objects from
|
|
152
|
+
* the ones the add-on's decoder produces.
|
|
153
|
+
*
|
|
154
|
+
* That is not just wasted bytes. Prototype patches apply to one copy while the
|
|
155
|
+
* other is the one actually used (the game's control-point decode patch fails
|
|
156
|
+
* exactly this way), and `instanceof` across the boundary silently returns
|
|
157
|
+
* false.
|
|
158
|
+
*
|
|
159
|
+
* Resolving these names from a single directory collapses them to one copy.
|
|
160
|
+
* The versions must be compatible, which is why the add-on pins the same ones
|
|
161
|
+
* the game does.
|
|
162
|
+
*/
|
|
163
|
+
export const dedupePlugin = (names, fromDir) => ({
|
|
164
|
+
name: 'dedupe',
|
|
165
|
+
setup(build) {
|
|
166
|
+
if (!names.length)
|
|
167
|
+
return;
|
|
168
|
+
const pattern = new RegExp(`^(${names.map(n => n.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')})(\\/.*)?$`);
|
|
169
|
+
build.onResolve({ filter: pattern }, async (args) => {
|
|
170
|
+
// Skip our own re-resolution, or we recurse forever.
|
|
171
|
+
if (args.pluginData === 'dedupe')
|
|
172
|
+
return undefined;
|
|
173
|
+
const result = await build.resolve(args.path, {
|
|
174
|
+
kind: args.kind,
|
|
175
|
+
resolveDir: fromDir,
|
|
176
|
+
pluginData: 'dedupe',
|
|
177
|
+
});
|
|
178
|
+
if (result.errors.length)
|
|
179
|
+
return undefined;
|
|
180
|
+
return { path: result.path, external: result.external };
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
/**
|
|
185
|
+
* Explicit asset imports: `import bytes from './thing.wasm?base64'`.
|
|
186
|
+
*
|
|
187
|
+
* The suffix does two jobs. It says at the import site how the file arrives -
|
|
188
|
+
* mirroring the `?url` convention the client uses under Vite - and it keeps
|
|
189
|
+
* TypeScript off a shipped `.wasm.d.ts`, which would otherwise win over the
|
|
190
|
+
* add-on's own `declare module` and report the import as having no default
|
|
191
|
+
* export.
|
|
192
|
+
*
|
|
193
|
+
* Resolution strips the suffix, so esbuild's loader for the real extension
|
|
194
|
+
* (`.wasm` -> base64) applies as normal.
|
|
195
|
+
*/
|
|
196
|
+
export const assetQueryPlugin = () => ({
|
|
197
|
+
name: 'asset-query',
|
|
198
|
+
setup(build) {
|
|
199
|
+
build.onResolve({ filter: /\?(base64|text)$/ }, async (args) => {
|
|
200
|
+
if (args.pluginData === 'asset-query')
|
|
201
|
+
return undefined;
|
|
202
|
+
const path = args.path.replace(/\?(base64|text)$/, '');
|
|
203
|
+
const result = await build.resolve(path, {
|
|
204
|
+
kind: args.kind,
|
|
205
|
+
resolveDir: args.resolveDir,
|
|
206
|
+
importer: args.importer,
|
|
207
|
+
pluginData: 'asset-query',
|
|
208
|
+
});
|
|
209
|
+
if (result.errors.length)
|
|
210
|
+
return { errors: result.errors };
|
|
211
|
+
return { path: result.path, external: result.external };
|
|
212
|
+
});
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
/**
|
|
216
|
+
* React from the game instead of a bundled copy: each React import becomes a small
|
|
217
|
+
* module reading the game's own React off `globalThis.ossIdle.react`, which the game
|
|
218
|
+
* sets before it loads any add-on. Its named exports are the installed React's, read
|
|
219
|
+
* here at build time, so `import { useState } from 'react'` keeps working.
|
|
220
|
+
*/
|
|
221
|
+
export const hostReactPlugin = (fromDir) => ({
|
|
222
|
+
name: 'host-react',
|
|
223
|
+
setup(build) {
|
|
224
|
+
const requireFrom = createRequire(resolve(fromDir, 'package.json'));
|
|
225
|
+
build.onResolve({ filter: /^react(-dom)?(\/(client|jsx-runtime))?$/ }, args => ({
|
|
226
|
+
path: args.path, namespace: 'oss-idle-react',
|
|
227
|
+
}));
|
|
228
|
+
build.onLoad({ filter: /.*/, namespace: 'oss-idle-react' }, args => {
|
|
229
|
+
const names = Object.keys(requireFrom(args.path))
|
|
230
|
+
.filter(name => name !== 'default' && /^[A-Za-z_$][\w$]*$/.test(name));
|
|
231
|
+
const contents = [
|
|
232
|
+
`const m = globalThis.ossIdle?.react?.[${JSON.stringify(args.path)}];`,
|
|
233
|
+
'if (!m) throw new Error("This add-on uses the game\'s React: update oss!idle to run it");',
|
|
234
|
+
'export default m;',
|
|
235
|
+
...names.map(name => `export const ${name} = m.${name};`),
|
|
236
|
+
].join('\n');
|
|
237
|
+
return { contents, loader: 'js' };
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { Addon, type AddonMeta, type Disposer } from './runtime/addon.js';
|
|
2
|
+
export { defineAddon, type AddonModule } from './runtime/define.js';
|
|
3
|
+
export { gameHost, type AddonHost, type EditorApi, type EditorMetadata, type EditorNote, type EditorRender, type EditorTimingPoint, } from './runtime/host.js';
|
|
4
|
+
export { buildMeta, type BuildMeta } from './runtime/buildMeta.js';
|
|
5
|
+
export { HotkeyManager, isolateInput, parseHotkey, type Hotkey, } from './runtime/input.js';
|
|
6
|
+
export { createSettings, type SettingsStore } from './runtime/settings.js';
|
|
7
|
+
export { gameDb, type SqlValue } from './bridge/db.js';
|
|
8
|
+
export { beatmapStore, type StoredSetMeta, type StoredVersion, } from './bridge/beatmaps.js';
|
|
9
|
+
export { gameCharacter, type CharacterRow } from './bridge/character.js';
|
|
10
|
+
export { playlists, type PlaylistRow } from './bridge/playlists.js';
|
|
11
|
+
export { playCountsSince, recentPlayCounts } from './bridge/scores.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EACN,QAAQ,EACR,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EACN,aAAa,EACb,YAAY,EACZ,WAAW,EACX,KAAK,MAAM,GACX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3E,OAAO,EAAE,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EACN,YAAY,EACZ,KAAK,aAAa,EAClB,KAAK,aAAa,GAClB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { Addon } from './runtime/addon.js';
|
|
2
|
+
export { defineAddon } from './runtime/define.js';
|
|
3
|
+
export { gameHost, } from './runtime/host.js';
|
|
4
|
+
export { buildMeta } from './runtime/buildMeta.js';
|
|
5
|
+
export { HotkeyManager, isolateInput, parseHotkey, } from './runtime/input.js';
|
|
6
|
+
export { createSettings } from './runtime/settings.js';
|
|
7
|
+
export { gameDb } from './bridge/db.js';
|
|
8
|
+
export { beatmapStore, } from './bridge/beatmaps.js';
|
|
9
|
+
export { gameCharacter } from './bridge/character.js';
|
|
10
|
+
export { playlists } from './bridge/playlists.js';
|
|
11
|
+
export { playCountsSince, recentPlayCounts } from './bridge/scores.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createOverlay, OVERLAY_Z_INDEX, type Overlay, type OverlayOptions, } from './overlay.js';
|
|
2
|
+
export { ReactAddon, type ReactAddonMeta } from './reactAddon.js';
|
|
3
|
+
export { usePersistedSettings, type SettingsHandle } from './useSettings.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,aAAa,EACb,eAAe,EACf,KAAK,OAAO,EACZ,KAAK,cAAc,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The overlay host.
|
|
3
|
+
*
|
|
4
|
+
* An add-on shares one document with the whole game, so a naive `<div>` on
|
|
5
|
+
* `document.body` inherits the client's styles and leaks its own back. The host
|
|
6
|
+
* therefore attaches a **shadow root** and renders inside it: the game's CSS
|
|
7
|
+
* stops at the boundary, and the add-on's stylesheet cannot restyle the game.
|
|
8
|
+
*
|
|
9
|
+
* Input is isolated at the host element, where events from inside the shadow
|
|
10
|
+
* tree bubble through - see `runtime/input.ts` for why bubble phase is the
|
|
11
|
+
* right place for that.
|
|
12
|
+
*/
|
|
13
|
+
import type { Disposer } from '../runtime/addon.js';
|
|
14
|
+
/**
|
|
15
|
+
* Default stacking order: above every piece of game UI, below the cursor.
|
|
16
|
+
*
|
|
17
|
+
* The client draws its own osu! cursor as a DOM element (`.cursor`, z-index
|
|
18
|
+
* 100000, with `.cursor-trail` at 99999) and hides the system one with
|
|
19
|
+
* `html.custom-cursor * { cursor: none !important }`. An overlay stacked above
|
|
20
|
+
* that layer breaks the pointer twice over: it covers the drawn cursor, and -
|
|
21
|
+
* because `cursor` is an inherited property - the `cursor: none` on the host
|
|
22
|
+
* inherits through the shadow boundary, so the system cursor stays hidden too.
|
|
23
|
+
* The player is left with no pointer at all, flickering into view only over
|
|
24
|
+
* translucent parts of the overlay.
|
|
25
|
+
*
|
|
26
|
+
* Sitting below the cursor layer instead means the osu! cursor draws over the
|
|
27
|
+
* overlay exactly as it does over the rest of the game. The game's own UI tops
|
|
28
|
+
* out around 20000, so this clears it comfortably.
|
|
29
|
+
*/
|
|
30
|
+
export declare const OVERLAY_Z_INDEX = 50000;
|
|
31
|
+
export type OverlayOptions = {
|
|
32
|
+
/** Element id, so a stale host from a failed unmount can be reclaimed. */
|
|
33
|
+
id: string;
|
|
34
|
+
/** CSS for the shadow tree. The bundler inlines `.css` imports into a string. */
|
|
35
|
+
styles?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Stacking order against the game's own layers. Defaults to
|
|
38
|
+
* {@link OVERLAY_Z_INDEX}; raising it above 99999 will hide the player's
|
|
39
|
+
* cursor.
|
|
40
|
+
*/
|
|
41
|
+
zIndex?: number;
|
|
42
|
+
};
|
|
43
|
+
export type Overlay = {
|
|
44
|
+
/** The element in the page. Input isolation is bound here. */
|
|
45
|
+
host: HTMLElement;
|
|
46
|
+
/** The shadow root React renders into. */
|
|
47
|
+
container: HTMLElement;
|
|
48
|
+
dispose: Disposer;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Create the overlay host. Idempotent by `id`: a host left behind by a crashed
|
|
52
|
+
* unmount is removed rather than duplicated, so enable/disable cycles cannot
|
|
53
|
+
* stack overlays.
|
|
54
|
+
*/
|
|
55
|
+
export declare const createOverlay: ({ id, styles, zIndex }: OverlayOptions) => Overlay;
|
|
56
|
+
//# sourceMappingURL=overlay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/react/overlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,eAAe,QAAS,CAAC;AAEtC,MAAM,MAAM,cAAc,GAAG;IAC5B,0EAA0E;IAC1E,EAAE,EAAE,MAAM,CAAC;IACX,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACrB,8DAA8D;IAC9D,IAAI,EAAE,WAAW,CAAC;IAClB,0CAA0C;IAC1C,SAAS,EAAE,WAAW,CAAC;IACvB,OAAO,EAAE,QAAQ,CAAC;CAClB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,wBAA0C,cAAc,KAAG,OAkCxF,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The overlay host.
|
|
3
|
+
*
|
|
4
|
+
* An add-on shares one document with the whole game, so a naive `<div>` on
|
|
5
|
+
* `document.body` inherits the client's styles and leaks its own back. The host
|
|
6
|
+
* therefore attaches a **shadow root** and renders inside it: the game's CSS
|
|
7
|
+
* stops at the boundary, and the add-on's stylesheet cannot restyle the game.
|
|
8
|
+
*
|
|
9
|
+
* Input is isolated at the host element, where events from inside the shadow
|
|
10
|
+
* tree bubble through - see `runtime/input.ts` for why bubble phase is the
|
|
11
|
+
* right place for that.
|
|
12
|
+
*/
|
|
13
|
+
import { isolateInput } from '../runtime/input.js';
|
|
14
|
+
/**
|
|
15
|
+
* Default stacking order: above every piece of game UI, below the cursor.
|
|
16
|
+
*
|
|
17
|
+
* The client draws its own osu! cursor as a DOM element (`.cursor`, z-index
|
|
18
|
+
* 100000, with `.cursor-trail` at 99999) and hides the system one with
|
|
19
|
+
* `html.custom-cursor * { cursor: none !important }`. An overlay stacked above
|
|
20
|
+
* that layer breaks the pointer twice over: it covers the drawn cursor, and -
|
|
21
|
+
* because `cursor` is an inherited property - the `cursor: none` on the host
|
|
22
|
+
* inherits through the shadow boundary, so the system cursor stays hidden too.
|
|
23
|
+
* The player is left with no pointer at all, flickering into view only over
|
|
24
|
+
* translucent parts of the overlay.
|
|
25
|
+
*
|
|
26
|
+
* Sitting below the cursor layer instead means the osu! cursor draws over the
|
|
27
|
+
* overlay exactly as it does over the rest of the game. The game's own UI tops
|
|
28
|
+
* out around 20000, so this clears it comfortably.
|
|
29
|
+
*/
|
|
30
|
+
export const OVERLAY_Z_INDEX = 50_000;
|
|
31
|
+
/**
|
|
32
|
+
* Create the overlay host. Idempotent by `id`: a host left behind by a crashed
|
|
33
|
+
* unmount is removed rather than duplicated, so enable/disable cycles cannot
|
|
34
|
+
* stack overlays.
|
|
35
|
+
*/
|
|
36
|
+
export const createOverlay = ({ id, styles, zIndex = OVERLAY_Z_INDEX }) => {
|
|
37
|
+
document.getElementById(id)?.remove();
|
|
38
|
+
const host = document.createElement('div');
|
|
39
|
+
host.id = id;
|
|
40
|
+
// `pointer-events: none` keeps the (full-screen) host from swallowing clicks
|
|
41
|
+
// meant for the game; the rendered panels turn it back on for themselves.
|
|
42
|
+
host.style.cssText =
|
|
43
|
+
`position:fixed;inset:0;z-index:${zIndex};pointer-events:none;`;
|
|
44
|
+
const shadow = host.attachShadow({ mode: 'open' });
|
|
45
|
+
if (styles) {
|
|
46
|
+
const sheet = document.createElement('style');
|
|
47
|
+
sheet.textContent = styles;
|
|
48
|
+
shadow.append(sheet);
|
|
49
|
+
}
|
|
50
|
+
const container = document.createElement('div');
|
|
51
|
+
container.style.cssText = 'position:absolute;inset:0;pointer-events:none;';
|
|
52
|
+
shadow.append(container);
|
|
53
|
+
document.body.append(host);
|
|
54
|
+
const releaseInput = isolateInput(host);
|
|
55
|
+
return {
|
|
56
|
+
host,
|
|
57
|
+
container,
|
|
58
|
+
dispose: () => {
|
|
59
|
+
releaseInput();
|
|
60
|
+
host.remove();
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
};
|