@dolphy-app/extension-sdk 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -12
- package/dist/client.d.ts +40 -0
- package/dist/client.js +67 -0
- package/dist/define-entry-BTz2F3qv.js +22 -0
- package/dist/define-entry-lsuxzKdD.d.ts +77 -0
- package/dist/index-I_CXC6Lj.d.ts +1958 -0
- package/dist/index.d.ts +7 -103
- package/dist/index.js +5 -80
- package/dist/react.d.ts +51 -0
- package/dist/react.js +112 -0
- package/dist/rpc.d.ts +28 -0
- package/dist/rpc.js +37 -0
- package/dist/testing.d.ts +248 -121
- package/dist/testing.js +779 -241
- package/docs/debugging.md +228 -0
- package/docs/no-build.md +135 -0
- package/docs/quick-start.md +194 -0
- package/docs/recipe-command-panel.md +355 -0
- package/docs/recipe-event-storage.md +326 -0
- package/docs/recipe-exercise-type.md +467 -0
- package/docs/recipe-hooks.md +158 -0
- package/docs/recipe-import-export.md +236 -0
- package/docs/recipe-mountable.md +322 -0
- package/docs/recipe-react.md +352 -0
- package/docs/recipe-rpc-and-app.md +366 -0
- package/docs/recipe-settings.md +183 -0
- package/docs/recipe-theme.md +148 -0
- package/docs/recipe-when-dependencies.md +264 -0
- package/package.json +31 -8
- package/dist/answer-element-BOQcuxYh.js +0 -114
- package/dist/answer-view-D6wnyThb.d.ts +0 -28
- package/dist/runtime.d.ts +0 -14
- package/dist/runtime.js +0 -18
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# Recipe: visibility conditions, a widget and dependencies
|
|
2
|
+
|
|
3
|
+
Show a command or a panel only where it makes sense (`when`), draw a component
|
|
4
|
+
into a screen of the app (an injection), and require another extension
|
|
5
|
+
(`dependencies`). This recipe has no template of its own: start from `blank`
|
|
6
|
+
and replace the files below, which are checked as a whole project. See
|
|
7
|
+
[quick-start.md](quick-start.md) for the commands.
|
|
8
|
+
|
|
9
|
+
## The manifest
|
|
10
|
+
|
|
11
|
+
File `extension.json` (when-dependencies):
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"$schema": "./node_modules/@dolphy-app/extension-api/dist/extension.schema.json",
|
|
16
|
+
"id": "acme.hello",
|
|
17
|
+
"version": "0.1.0",
|
|
18
|
+
"apiVersion": 1,
|
|
19
|
+
"name": "Hello report",
|
|
20
|
+
"description": "A report command that appears only on the Courses screen.",
|
|
21
|
+
"author": "your-github-login",
|
|
22
|
+
"tags": ["productivity"],
|
|
23
|
+
"dependencies": [
|
|
24
|
+
{ "id": "acme.cards", "range": ">=1.0.0 <2.0.0" },
|
|
25
|
+
{ "id": "dolphy.choice" }
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`dependencies` is the only part of this recipe that lives in the manifest: up to
|
|
31
|
+
16 extensions that must be present, enabled, loaded and in the version `range`
|
|
32
|
+
(comparators separated by a space, such as `>=1.0.0 <2.0.0`; no `^` or `~`).
|
|
33
|
+
Otherwise the extension is shown as "dependencies not met" in Settings →
|
|
34
|
+
Extensions, with the missing, disabled or mismatched one named, and contributes
|
|
35
|
+
nothing. Disabling or enabling a dependency updates the dependents at once.
|
|
36
|
+
|
|
37
|
+
Nothing installs a dependency for the user, and extensions cannot call each
|
|
38
|
+
other: a dependency only says "this must be there". A repeat and a dependency on
|
|
39
|
+
yourself are manifest errors; extensions that depend on each other in a circle
|
|
40
|
+
are not loaded.
|
|
41
|
+
|
|
42
|
+
## The code
|
|
43
|
+
|
|
44
|
+
File `src/index.ts` (when-dependencies):
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
export { client } from './client.ts';
|
|
48
|
+
export { server } from './server.ts';
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
File `src/server.ts` (when-dependencies):
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { defineServer } from '@dolphy-app/extension-sdk';
|
|
55
|
+
|
|
56
|
+
export const server = defineServer((s) => {
|
|
57
|
+
s.registerCommand({
|
|
58
|
+
id: 'acme.hello.report',
|
|
59
|
+
title: { en: 'Course report', ru: 'Отчёт по курсу' },
|
|
60
|
+
when: "route == 'courses' && course.active",
|
|
61
|
+
run: () => ({ courses: 1 }),
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
File `src/client.ts` (when-dependencies):
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { anchorSelector, defineClient } from '@dolphy-app/extension-sdk';
|
|
70
|
+
import { Board } from './board.ts';
|
|
71
|
+
import { PlanNote } from './plan-note.ts';
|
|
72
|
+
|
|
73
|
+
export const client = defineClient((c) => {
|
|
74
|
+
c.addPanel({
|
|
75
|
+
id: 'acme.hello.board',
|
|
76
|
+
title: { en: 'Course board', ru: 'Доска курса' },
|
|
77
|
+
when: 'course.active && !session.active',
|
|
78
|
+
component: Board,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// drawn inside the "Daily plan" screen, at the anchor the app keeps stable
|
|
82
|
+
c.addInjection({
|
|
83
|
+
id: 'acme.hello.plan-note',
|
|
84
|
+
target: anchorSelector('dailyPlan'),
|
|
85
|
+
component: PlanNote,
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
File `src/board.ts` (when-dependencies):
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { defineComponent, h } from 'vue';
|
|
94
|
+
|
|
95
|
+
export const Board = defineComponent({
|
|
96
|
+
setup: () => () => h('p', 'Board'),
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
File `src/plan-note.ts` (when-dependencies):
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { useInjection } from '@dolphy-app/extension-sdk/client';
|
|
104
|
+
import { defineComponent, h } from 'vue';
|
|
105
|
+
|
|
106
|
+
// `useInjection()` tells the component where the app drew it
|
|
107
|
+
export const PlanNote = defineComponent({
|
|
108
|
+
setup() {
|
|
109
|
+
const injection = useInjection();
|
|
110
|
+
return () => h('p', `Drawn at the daily plan: ${injection.position}`);
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- `when` is a boolean expression over five keys of the app window: `route`
|
|
116
|
+
(the name of the current screen, one of `WHEN_ROUTES`), `course.active` (one
|
|
117
|
+
course is in focus), `session.active` (a study session is open), `locale`
|
|
118
|
+
(`ru` or `en`) and `theme.dark`. Operators: `==`, `!=`, `in ('a', 'b')`, `&&`,
|
|
119
|
+
`||`, `!` and parentheses; strings are in single quotes. At most 200
|
|
120
|
+
characters. It goes on a command (`server.registerCommand`, also on one of its
|
|
121
|
+
`keybindings`), a client command (`client.addCommand`) or a panel
|
|
122
|
+
(`client.addPanel`). An unknown key or value, a wrong type or a syntax error
|
|
123
|
+
fails the registration with the position in the message.
|
|
124
|
+
- While the condition is false a command is not in the palette and its keys do
|
|
125
|
+
nothing, and a panel's menu item is hidden. The value follows the route, the
|
|
126
|
+
course, the session, the language and the theme without a reload. Your own code
|
|
127
|
+
still reaches the command (`panel.call`) and the panel (`openPanel`): `when`
|
|
128
|
+
hides, it does not forbid.
|
|
129
|
+
- `client.addInjection({ id, target, position?, component })` draws the
|
|
130
|
+
component at every element that matches the CSS selector `target`: `before` or
|
|
131
|
+
`after` it, or inside it as its first (`prepend`) or last (`append`, the
|
|
132
|
+
default) child. The window watches its DOM: the component is mounted when a
|
|
133
|
+
target appears and removed when it goes away or when the extension is
|
|
134
|
+
unloaded. An injection has no `when`: it exists where the target does.
|
|
135
|
+
- `anchorSelector('dailyPlan')` is `[data-ext-anchor="dailyPlan"]`, the place
|
|
136
|
+
the app marks in the "Daily plan" screen and keeps stable. Any other selector
|
|
137
|
+
depends on the markup of the app, which can change between versions, so the
|
|
138
|
+
injection may silently stop finding its target after an update; prefer an
|
|
139
|
+
anchor.
|
|
140
|
+
- The injected component runs in the app's own tree: `inject`, Vuetify, the
|
|
141
|
+
theme and the language work. A failure shows an error card in its place and
|
|
142
|
+
does not touch the rest of the window.
|
|
143
|
+
|
|
144
|
+
## The test
|
|
145
|
+
|
|
146
|
+
File `test/index.test.ts` (when-dependencies):
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// @vitest-environment happy-dom
|
|
150
|
+
import {
|
|
151
|
+
INJECTION_HANDLE_KEY,
|
|
152
|
+
anchorSelector,
|
|
153
|
+
evaluateWhen,
|
|
154
|
+
parseWhen,
|
|
155
|
+
} from '@dolphy-app/extension-sdk';
|
|
156
|
+
import type { WhenContext } from '@dolphy-app/extension-sdk';
|
|
157
|
+
import {
|
|
158
|
+
createTestClient,
|
|
159
|
+
createTestServer,
|
|
160
|
+
} from '@dolphy-app/extension-sdk/testing';
|
|
161
|
+
import { describe, expect, it } from 'vitest';
|
|
162
|
+
import { createApp, h } from 'vue';
|
|
163
|
+
import manifest from '../extension.json';
|
|
164
|
+
import { client, server } from '../src/index.ts';
|
|
165
|
+
import { PlanNote } from '../src/plan-note.ts';
|
|
166
|
+
|
|
167
|
+
const context = (overrides: Partial<WhenContext> = {}): WhenContext => ({
|
|
168
|
+
route: 'courses',
|
|
169
|
+
'course.active': true,
|
|
170
|
+
'session.active': false,
|
|
171
|
+
locale: 'en',
|
|
172
|
+
'theme.dark': false,
|
|
173
|
+
...overrides,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const parsed = (when: string | null | undefined) => {
|
|
177
|
+
if (when === null || when === undefined) throw new Error('no condition');
|
|
178
|
+
return parseWhen(when);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
describe('acme.hello: when', () => {
|
|
182
|
+
it('shows the report on the Courses screen with a course in focus', async () => {
|
|
183
|
+
const running = await createTestServer(server, {
|
|
184
|
+
extensionId: 'acme.hello',
|
|
185
|
+
});
|
|
186
|
+
const report = running.registration.commands.find(
|
|
187
|
+
({ id }) => id === 'acme.hello.report',
|
|
188
|
+
);
|
|
189
|
+
const when = parsed(report?.when);
|
|
190
|
+
expect(evaluateWhen(when, context())).toBe(true);
|
|
191
|
+
expect(evaluateWhen(when, context({ route: 'daily-plan' }))).toBe(false);
|
|
192
|
+
expect(evaluateWhen(when, context({ 'course.active': false }))).toBe(false);
|
|
193
|
+
await running.dispose();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('hides the board during a study session', async () => {
|
|
197
|
+
const running = await createTestClient(client, {
|
|
198
|
+
extensionId: 'acme.hello',
|
|
199
|
+
});
|
|
200
|
+
const board = running.panels.find(({ id }) => id === 'acme.hello.board');
|
|
201
|
+
const when = parsed(board?.when);
|
|
202
|
+
expect(evaluateWhen(when, context())).toBe(true);
|
|
203
|
+
expect(evaluateWhen(when, context({ 'session.active': true }))).toBe(false);
|
|
204
|
+
await running.dispose();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('reports a typo in a key with its position', () => {
|
|
208
|
+
expect(() => parseWhen("rout == 'courses'")).toThrow();
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('acme.hello: injection', () => {
|
|
213
|
+
it('is drawn at the anchor of the daily plan', async () => {
|
|
214
|
+
const running = await createTestClient(client, {
|
|
215
|
+
extensionId: 'acme.hello',
|
|
216
|
+
});
|
|
217
|
+
expect(running.injections).toEqual([
|
|
218
|
+
{
|
|
219
|
+
id: 'acme.hello.plan-note',
|
|
220
|
+
target: anchorSelector('dailyPlan'),
|
|
221
|
+
position: 'append',
|
|
222
|
+
component: PlanNote,
|
|
223
|
+
},
|
|
224
|
+
]);
|
|
225
|
+
await running.dispose();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('the component reads where the app drew it', () => {
|
|
229
|
+
const host = document.createElement('div');
|
|
230
|
+
const app = createApp({ render: () => h(PlanNote) });
|
|
231
|
+
app.provide(INJECTION_HANDLE_KEY, {
|
|
232
|
+
target: document.createElement('div'),
|
|
233
|
+
position: 'append',
|
|
234
|
+
});
|
|
235
|
+
app.mount(host);
|
|
236
|
+
expect(host.textContent).toBe('Drawn at the daily plan: append');
|
|
237
|
+
app.unmount();
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('acme.hello: dependencies', () => {
|
|
242
|
+
it('names each required extension once, and not itself', () => {
|
|
243
|
+
const ids = manifest.dependencies.map(({ id }) => id);
|
|
244
|
+
expect(new Set(ids).size).toBe(ids.length);
|
|
245
|
+
expect(ids).not.toContain(manifest.id);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
`createTestServer` and `createTestClient` hand the test what the code
|
|
251
|
+
registered: `running.registration.commands` for the server (with `when` as
|
|
252
|
+
registered) and `running.panels` and `running.injections` for the client. The
|
|
253
|
+
test feeds that text to `parseWhen` and `evaluateWhen`, the functions the app
|
|
254
|
+
uses; `evaluateWhen` is pure, so pass an object with the five keys. The
|
|
255
|
+
dependencies are manifest data, so the test reads `extension.json`.
|
|
256
|
+
|
|
257
|
+
## Try and ship
|
|
258
|
+
|
|
259
|
+
Run `pnpm build` and `pnpm validate`, which fails on a bad `dependencies`
|
|
260
|
+
entry. The test parses every `when`, so `pnpm test` catches a typo before the
|
|
261
|
+
app does (the app fails the registration with the position). In the app the
|
|
262
|
+
extension's row in Settings → Extensions shows "dependencies not met" until
|
|
263
|
+
`acme.cards` is installed and enabled. Open the Daily plan screen to see the
|
|
264
|
+
injected note.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dolphy-app/extension-sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "SDK for extension authors:
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "SDK for extension authors: defineServer, defineClient, defineRpc, usePanel, useInjection, useApp, useEngine, useRpc, defineMountable, callRpc, reactComponent, test harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"exports": {
|
|
@@ -9,22 +9,45 @@
|
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
10
|
"default": "./dist/index.js"
|
|
11
11
|
},
|
|
12
|
-
"./
|
|
13
|
-
"types": "./dist/
|
|
14
|
-
"default": "./dist/
|
|
12
|
+
"./client": {
|
|
13
|
+
"types": "./dist/client.d.ts",
|
|
14
|
+
"default": "./dist/client.js"
|
|
15
|
+
},
|
|
16
|
+
"./rpc": {
|
|
17
|
+
"types": "./dist/rpc.d.ts",
|
|
18
|
+
"default": "./dist/rpc.js"
|
|
15
19
|
},
|
|
16
20
|
"./testing": {
|
|
17
21
|
"types": "./dist/testing.d.ts",
|
|
18
22
|
"default": "./dist/testing.js"
|
|
23
|
+
},
|
|
24
|
+
"./react": {
|
|
25
|
+
"types": "./dist/react.d.ts",
|
|
26
|
+
"default": "./dist/react.js"
|
|
19
27
|
}
|
|
20
28
|
},
|
|
21
29
|
"types": "./dist/index.d.ts",
|
|
22
30
|
"files": [
|
|
23
|
-
"dist"
|
|
31
|
+
"dist",
|
|
32
|
+
"docs"
|
|
24
33
|
],
|
|
25
34
|
"dependencies": {
|
|
26
|
-
"@dolphy-app/extension-api": "0.
|
|
27
|
-
"ajv": "^8"
|
|
35
|
+
"@dolphy-app/extension-api": "0.5.0",
|
|
36
|
+
"ajv": "^8",
|
|
37
|
+
"zod": "4.6.5"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"vue": "^3.5",
|
|
41
|
+
"react": "^19",
|
|
42
|
+
"react-dom": "^19"
|
|
43
|
+
},
|
|
44
|
+
"peerDependenciesMeta": {
|
|
45
|
+
"react": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"react-dom": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
28
51
|
},
|
|
29
52
|
"engines": {
|
|
30
53
|
"node": ">=22.12"
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { ANSWER_EVENT, ELEMENT_NAME_PATTERN } from "@dolphy-app/extension-api";
|
|
2
|
-
|
|
3
|
-
//#region packages/extension-sdk/src/answer-element.ts
|
|
4
|
-
const logFailure = (tag, message, error) => {
|
|
5
|
-
console.error({
|
|
6
|
-
error,
|
|
7
|
-
tag
|
|
8
|
-
}, message);
|
|
9
|
-
};
|
|
10
|
-
const createAnswerElementClass = (tag, answerView) => class AnswerElement extends HTMLElement {
|
|
11
|
-
#root = this.attachShadow({ mode: "open" });
|
|
12
|
-
#props = {
|
|
13
|
-
view: void 0,
|
|
14
|
-
value: void 0,
|
|
15
|
-
disabled: false,
|
|
16
|
-
verdict: null
|
|
17
|
-
};
|
|
18
|
-
#instance = null;
|
|
19
|
-
#isFlushScheduled = false;
|
|
20
|
-
get view() {
|
|
21
|
-
return this.#props.view;
|
|
22
|
-
}
|
|
23
|
-
set view(view) {
|
|
24
|
-
this.#change({ view });
|
|
25
|
-
}
|
|
26
|
-
get value() {
|
|
27
|
-
return this.#props.value;
|
|
28
|
-
}
|
|
29
|
-
set value(value) {
|
|
30
|
-
this.#change({ value });
|
|
31
|
-
}
|
|
32
|
-
get disabled() {
|
|
33
|
-
return this.#props.disabled;
|
|
34
|
-
}
|
|
35
|
-
set disabled(disabled) {
|
|
36
|
-
this.#change({ disabled });
|
|
37
|
-
}
|
|
38
|
-
get verdict() {
|
|
39
|
-
return this.#props.verdict;
|
|
40
|
-
}
|
|
41
|
-
set verdict(verdict) {
|
|
42
|
-
this.#change({ verdict });
|
|
43
|
-
}
|
|
44
|
-
connectedCallback() {
|
|
45
|
-
if (this.#instance !== null) return;
|
|
46
|
-
const readLabel = () => this.getAttribute("aria-label");
|
|
47
|
-
const api = {
|
|
48
|
-
root: this.#root,
|
|
49
|
-
get label() {
|
|
50
|
-
return readLabel();
|
|
51
|
-
},
|
|
52
|
-
setAnswer: (value, complete) => {
|
|
53
|
-
const detail = {
|
|
54
|
-
value,
|
|
55
|
-
complete
|
|
56
|
-
};
|
|
57
|
-
this.#emit(ANSWER_EVENT.change, detail);
|
|
58
|
-
},
|
|
59
|
-
submit: () => this.#emit(ANSWER_EVENT.submit, void 0)
|
|
60
|
-
};
|
|
61
|
-
try {
|
|
62
|
-
this.#instance = answerView.mount(api, Object.freeze({ ...this.#props }));
|
|
63
|
-
} catch (error) {
|
|
64
|
-
logFailure(tag, "answer element failed to mount", error);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
disconnectedCallback() {
|
|
68
|
-
const instance = this.#instance;
|
|
69
|
-
this.#instance = null;
|
|
70
|
-
if (instance === null) return;
|
|
71
|
-
try {
|
|
72
|
-
instance.destroy?.();
|
|
73
|
-
} catch (error) {
|
|
74
|
-
logFailure(tag, "answer element failed to destroy", error);
|
|
75
|
-
}
|
|
76
|
-
this.#root.replaceChildren();
|
|
77
|
-
}
|
|
78
|
-
#change(patch) {
|
|
79
|
-
this.#props = {
|
|
80
|
-
...this.#props,
|
|
81
|
-
...patch
|
|
82
|
-
};
|
|
83
|
-
if (this.#instance === null || this.#isFlushScheduled) return;
|
|
84
|
-
this.#isFlushScheduled = true;
|
|
85
|
-
queueMicrotask(() => this.#flush());
|
|
86
|
-
}
|
|
87
|
-
#flush() {
|
|
88
|
-
this.#isFlushScheduled = false;
|
|
89
|
-
const instance = this.#instance;
|
|
90
|
-
if (instance === null) return;
|
|
91
|
-
try {
|
|
92
|
-
instance.update(Object.freeze({ ...this.#props }));
|
|
93
|
-
} catch (error) {
|
|
94
|
-
logFailure(tag, "answer element failed to update", error);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
#emit(name, detail) {
|
|
98
|
-
this.dispatchEvent(new CustomEvent(name, {
|
|
99
|
-
detail,
|
|
100
|
-
bubbles: true,
|
|
101
|
-
composed: true
|
|
102
|
-
}));
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
/** Defines the kind's custom element; calling again with the same tag changes nothing. */
|
|
106
|
-
const registerAnswerView = (tag, view) => {
|
|
107
|
-
if (!ELEMENT_NAME_PATTERN.test(tag)) throw new TypeError(`invalid custom element name '${tag}'`);
|
|
108
|
-
if (typeof view?.mount !== "function") throw new TypeError(`answer view for '${tag}' has no mount()`);
|
|
109
|
-
if (customElements.get(tag) !== void 0) return;
|
|
110
|
-
customElements.define(tag, createAnswerElementClass(tag, view));
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
//#endregion
|
|
114
|
-
export { registerAnswerView as n, createAnswerElementClass as t };
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { AnswerElementProps } from "@dolphy-app/extension-api";
|
|
2
|
-
//#region packages/extension-sdk/src/answer-view.d.ts
|
|
3
|
-
interface AnswerViewApi {
|
|
4
|
-
readonly root: ShadowRoot;
|
|
5
|
-
/** `aria-label` of the host element set by the app; `null` if none. */
|
|
6
|
-
readonly label: string | null;
|
|
7
|
-
/** Reports the current answer to the app: the `dolphy-answer-change` event. */
|
|
8
|
-
setAnswer(value: unknown, complete: boolean): void;
|
|
9
|
-
/** Asks the app to submit the answer: the `dolphy-answer-submit` event. */
|
|
10
|
-
submit(): void;
|
|
11
|
-
}
|
|
12
|
-
interface AnswerViewInstance {
|
|
13
|
-
/** Called when `view`/`value`/`disabled`/`verdict` change. */
|
|
14
|
-
update(props: AnswerElementProps): void;
|
|
15
|
-
destroy?(): void;
|
|
16
|
-
}
|
|
17
|
-
type MountAnswerView = (api: AnswerViewApi, props: AnswerElementProps) => AnswerViewInstance;
|
|
18
|
-
/** Description of an answer input view: side-effect-free data; the build registers the element. */
|
|
19
|
-
interface AnswerView {
|
|
20
|
-
readonly mount: MountAnswerView;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Entry `views[<exercise kind id>]` in `src/index.ts`. Registers nothing:
|
|
24
|
-
* the custom element with the manifest tag is defined by the build's browser file.
|
|
25
|
-
*/
|
|
26
|
-
declare const defineAnswerView: (mount: MountAnswerView) => AnswerView;
|
|
27
|
-
//#endregion
|
|
28
|
-
export { defineAnswerView as a, MountAnswerView as i, AnswerViewApi as n, AnswerViewInstance as r, AnswerView as t };
|
package/dist/runtime.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { t as AnswerView } from "./answer-view-D6wnyThb.js";
|
|
2
|
-
import { MarkdownRendererModule, PanelModule } from "@dolphy-app/extension-api";
|
|
3
|
-
//#region packages/extension-sdk/src/answer-element.d.ts
|
|
4
|
-
/** Defines the kind's custom element; calling again with the same tag changes nothing. */
|
|
5
|
-
export declare const registerAnswerView: (tag: string, view: AnswerView) => void;
|
|
6
|
-
//#endregion
|
|
7
|
-
//#region packages/extension-sdk/src/runtime.d.ts
|
|
8
|
-
type PanelEntry = PanelModule<HTMLElement>;
|
|
9
|
-
type MarkdownEntry = MarkdownRendererModule<HTMLElement>;
|
|
10
|
-
/** Panel module that selects the `panels` entry by `ctx.panelId` (for a file shared by several panels). */
|
|
11
|
-
export declare const dispatchPanels: (panels: Readonly<Record<string, PanelEntry>>) => PanelEntry;
|
|
12
|
-
/** Renderer module that selects the `markdown` entry by block language. */
|
|
13
|
-
export declare const dispatchMarkdown: (renderers: Readonly<Record<string, MarkdownEntry>>) => MarkdownEntry;
|
|
14
|
-
//#endregion
|
package/dist/runtime.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { n as registerAnswerView } from "./answer-element-BOQcuxYh.js";
|
|
2
|
-
|
|
3
|
-
//#region packages/extension-sdk/src/runtime.ts
|
|
4
|
-
/** Panel module that selects the `panels` entry by `ctx.panelId` (for a file shared by several panels). */
|
|
5
|
-
const dispatchPanels = (panels) => ({ mount(container, context) {
|
|
6
|
-
const panel = panels[context.panelId];
|
|
7
|
-
if (panel === void 0) throw new Error(`panel '${context.panelId}' is not exported`);
|
|
8
|
-
return panel.mount(container, context);
|
|
9
|
-
} });
|
|
10
|
-
/** Renderer module that selects the `markdown` entry by block language. */
|
|
11
|
-
const dispatchMarkdown = (renderers) => ({ render(source, container, context) {
|
|
12
|
-
const renderer = renderers[context.language];
|
|
13
|
-
if (renderer === void 0) throw new Error(`markdown renderer '${context.language}' is not exported`);
|
|
14
|
-
return renderer.render(source, container, context);
|
|
15
|
-
} });
|
|
16
|
-
|
|
17
|
-
//#endregion
|
|
18
|
-
export { dispatchMarkdown, dispatchPanels, registerAnswerView };
|