@dolphy-app/extension-sdk 0.4.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 +28 -14
- 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 -111
- package/dist/index.js +5 -86
- 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 +184 -241
- package/dist/testing.js +574 -464
- package/docs/debugging.md +82 -64
- package/docs/no-build.md +84 -48
- package/docs/quick-start.md +78 -56
- package/docs/recipe-command-panel.md +262 -118
- package/docs/recipe-event-storage.md +188 -128
- package/docs/recipe-exercise-type.md +271 -183
- package/docs/recipe-hooks.md +158 -0
- package/docs/recipe-import-export.md +48 -53
- 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 +122 -100
- package/docs/recipe-theme.md +72 -40
- package/docs/recipe-when-dependencies.md +188 -82
- package/package.json +29 -7
- package/dist/answer-element-BOQcuxYh.js +0 -114
- package/dist/answer-view-D6wnyThb.d.ts +0 -28
- package/dist/runtime.d.ts +0 -17
- package/dist/runtime.js +0 -24
- package/docs/recipe-ui-kit.md +0 -172
|
@@ -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,17 +0,0 @@
|
|
|
1
|
-
import { t as AnswerView } from "./answer-view-D6wnyThb.js";
|
|
2
|
-
import { MarkdownRendererModule, PanelModule, WidgetModule } 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 WidgetEntry = WidgetModule<HTMLElement>;
|
|
10
|
-
type MarkdownEntry = MarkdownRendererModule<HTMLElement>;
|
|
11
|
-
/** Panel module that selects the `panels` entry by `ctx.panelId` (for a file shared by several panels). */
|
|
12
|
-
export declare const dispatchPanels: (panels: Readonly<Record<string, PanelEntry>>) => PanelEntry;
|
|
13
|
-
/** Widget module that selects the `widgets` entry by `ctx.widgetId` (for a file shared by several widgets). */
|
|
14
|
-
export declare const dispatchWidgets: (widgets: Readonly<Record<string, WidgetEntry>>) => WidgetEntry;
|
|
15
|
-
/** Renderer module that selects the `markdown` entry by block language. */
|
|
16
|
-
export declare const dispatchMarkdown: (renderers: Readonly<Record<string, MarkdownEntry>>) => MarkdownEntry;
|
|
17
|
-
//#endregion
|
package/dist/runtime.js
DELETED
|
@@ -1,24 +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
|
-
/** Widget module that selects the `widgets` entry by `ctx.widgetId` (for a file shared by several widgets). */
|
|
11
|
-
const dispatchWidgets = (widgets) => ({ mount(container, context) {
|
|
12
|
-
const widget = widgets[context.widgetId];
|
|
13
|
-
if (widget === void 0) throw new Error(`widget '${context.widgetId}' is not exported`);
|
|
14
|
-
return widget.mount(container, context);
|
|
15
|
-
} });
|
|
16
|
-
/** Renderer module that selects the `markdown` entry by block language. */
|
|
17
|
-
const dispatchMarkdown = (renderers) => ({ render(source, container, context) {
|
|
18
|
-
const renderer = renderers[context.language];
|
|
19
|
-
if (renderer === void 0) throw new Error(`markdown renderer '${context.language}' is not exported`);
|
|
20
|
-
return renderer.render(source, container, context);
|
|
21
|
-
} });
|
|
22
|
-
|
|
23
|
-
//#endregion
|
|
24
|
-
export { dispatchMarkdown, dispatchPanels, dispatchWidgets, registerAnswerView };
|
package/docs/recipe-ui-kit.md
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
# Recipe: a panel on the UI kit
|
|
2
|
-
|
|
3
|
-
`@dolphy-app/extension-ui` gives a panel ready, accessible elements, so you do
|
|
4
|
-
not write markup, keyboard handling or dark-theme colours yourself. This recipe
|
|
5
|
-
has no template of its own: start from `blank`, install the kit
|
|
6
|
-
(`pnpm add @dolphy-app/extension-ui`) and replace the three files below, which
|
|
7
|
-
are checked as a whole project. See [quick-start.md](quick-start.md) for the
|
|
8
|
-
commands.
|
|
9
|
-
|
|
10
|
-
## The manifest
|
|
11
|
-
|
|
12
|
-
File `extension.json` (ui-kit):
|
|
13
|
-
|
|
14
|
-
```json
|
|
15
|
-
{
|
|
16
|
-
"$schema": "./node_modules/@dolphy-app/extension-api/dist/extension.schema.json",
|
|
17
|
-
"id": "acme.hello",
|
|
18
|
-
"version": "0.1.0",
|
|
19
|
-
"apiVersion": 1,
|
|
20
|
-
"name": "Hello notes",
|
|
21
|
-
"description": "A panel with a text field, a button and a list built on the UI kit.",
|
|
22
|
-
"author": "your-github-login",
|
|
23
|
-
"tags": ["productivity"],
|
|
24
|
-
"contributes": {
|
|
25
|
-
"panels": [{ "id": "acme.hello.view", "title": "Notes" }]
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## The code
|
|
31
|
-
|
|
32
|
-
File `src/index.ts` (ui-kit):
|
|
33
|
-
|
|
34
|
-
```ts
|
|
35
|
-
import { defineExtensionPanel } from '@dolphy-app/extension-sdk';
|
|
36
|
-
import type { ExtensionPanels } from '@dolphy-app/extension-sdk';
|
|
37
|
-
import {
|
|
38
|
-
button,
|
|
39
|
-
card,
|
|
40
|
-
emptyState,
|
|
41
|
-
list,
|
|
42
|
-
textField,
|
|
43
|
-
} from '@dolphy-app/extension-ui';
|
|
44
|
-
|
|
45
|
-
export const panels = {
|
|
46
|
-
'acme.hello.view': defineExtensionPanel({
|
|
47
|
-
mount(container) {
|
|
48
|
-
const notes: string[] = [];
|
|
49
|
-
let draft = '';
|
|
50
|
-
const status = document.createElement('p');
|
|
51
|
-
status.setAttribute('role', 'status');
|
|
52
|
-
const body = document.createElement('div');
|
|
53
|
-
// elements are static: draw the list again with replaceChildren
|
|
54
|
-
const render = () => {
|
|
55
|
-
body.replaceChildren(
|
|
56
|
-
notes.length === 0
|
|
57
|
-
? emptyState({
|
|
58
|
-
title: 'No notes yet',
|
|
59
|
-
description: 'Type a note and press Add.',
|
|
60
|
-
})
|
|
61
|
-
: list({
|
|
62
|
-
label: 'Notes',
|
|
63
|
-
emptyText: 'No notes',
|
|
64
|
-
items: notes.map((note, index) => ({
|
|
65
|
-
id: String(index),
|
|
66
|
-
label: note,
|
|
67
|
-
})),
|
|
68
|
-
onSelect: (id) => {
|
|
69
|
-
status.textContent = `Selected note ${Number(id) + 1}`;
|
|
70
|
-
},
|
|
71
|
-
}),
|
|
72
|
-
);
|
|
73
|
-
};
|
|
74
|
-
render();
|
|
75
|
-
container.append(
|
|
76
|
-
card({
|
|
77
|
-
title: 'Notes',
|
|
78
|
-
children: [
|
|
79
|
-
textField({
|
|
80
|
-
label: 'Note text',
|
|
81
|
-
onInput: (value) => {
|
|
82
|
-
draft = value;
|
|
83
|
-
},
|
|
84
|
-
}),
|
|
85
|
-
button({
|
|
86
|
-
label: 'Add',
|
|
87
|
-
variant: 'primary',
|
|
88
|
-
onClick: () => {
|
|
89
|
-
if (draft.trim() === '') return;
|
|
90
|
-
notes.push(draft.trim());
|
|
91
|
-
status.textContent = `Notes: ${notes.length}`;
|
|
92
|
-
render();
|
|
93
|
-
},
|
|
94
|
-
}),
|
|
95
|
-
body,
|
|
96
|
-
],
|
|
97
|
-
}),
|
|
98
|
-
status,
|
|
99
|
-
);
|
|
100
|
-
},
|
|
101
|
-
}),
|
|
102
|
-
} satisfies ExtensionPanels;
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
- `list`, `button`, `textField`, `select`, `toggle`, `card` and `emptyState`
|
|
106
|
-
return DOM elements you append yourself. Each has a role, a visible name and
|
|
107
|
-
keyboard control (a `list` is one tab stop, arrows move, Enter or Space
|
|
108
|
-
select).
|
|
109
|
-
- Colours and spacing come from the CSS variables of the frame theme, so the
|
|
110
|
-
light and the dark theme work with no code of yours. Text is always set as
|
|
111
|
-
`textContent`; the kit never parses markup.
|
|
112
|
-
- Elements keep no state of their own. To change a list, build a new one and
|
|
113
|
-
call `replaceWith` / `replaceChildren`.
|
|
114
|
-
- The whole kit is under 10 KiB gzipped, and it adds no permission: the panel
|
|
115
|
-
still runs in the isolated frame with no network.
|
|
116
|
-
|
|
117
|
-
## The test
|
|
118
|
-
|
|
119
|
-
File `test/index.test.ts` (ui-kit):
|
|
120
|
-
|
|
121
|
-
```ts
|
|
122
|
-
// @vitest-environment happy-dom
|
|
123
|
-
import { loadPanel } from '@dolphy-app/extension-sdk/testing';
|
|
124
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
125
|
-
import { panels } from '../src/index.ts';
|
|
126
|
-
|
|
127
|
-
const disposables: { dispose(): unknown }[] = [];
|
|
128
|
-
afterEach(async () => {
|
|
129
|
-
await Promise.all(disposables.splice(0).map((item) => item.dispose()));
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
describe('acme.hello: panel', () => {
|
|
133
|
-
it('starts with an empty state and lists an added note', async () => {
|
|
134
|
-
const panel = await loadPanel(panels, 'acme.hello.view');
|
|
135
|
-
disposables.push(panel);
|
|
136
|
-
expect(panel.container.textContent).toContain('No notes yet');
|
|
137
|
-
|
|
138
|
-
const input = panel.container.querySelector('input') as HTMLInputElement;
|
|
139
|
-
input.value = 'Buy milk';
|
|
140
|
-
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
141
|
-
const add = [...panel.container.querySelectorAll('button')].find(
|
|
142
|
-
(element) => element.textContent === 'Add',
|
|
143
|
-
) as HTMLButtonElement;
|
|
144
|
-
add.click();
|
|
145
|
-
|
|
146
|
-
const options = panel.container.querySelectorAll('[role="option"]');
|
|
147
|
-
expect(options).toHaveLength(1);
|
|
148
|
-
expect(options[0]?.textContent).toContain('Buy milk');
|
|
149
|
-
expect(panel.container.querySelector('[role="status"]')?.textContent).toBe(
|
|
150
|
-
'Notes: 1',
|
|
151
|
-
);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it('ignores an empty note', async () => {
|
|
155
|
-
const panel = await loadPanel(panels, 'acme.hello.view');
|
|
156
|
-
disposables.push(panel);
|
|
157
|
-
const add = [...panel.container.querySelectorAll('button')].find(
|
|
158
|
-
(element) => element.textContent === 'Add',
|
|
159
|
-
) as HTMLButtonElement;
|
|
160
|
-
add.click();
|
|
161
|
-
expect(panel.container.textContent).toContain('No notes yet');
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
`loadPanel` mounts the panel in happy-dom the way the frame does. The kit's
|
|
167
|
-
own tests check its roles and keys; test what your panel does with them.
|
|
168
|
-
|
|
169
|
-
## Try and ship
|
|
170
|
-
|
|
171
|
-
Run `pnpm dev`, start the app with `DOLPHY_DEV_EXTENSIONS` and open the panel
|
|
172
|
-
from the sidebar. Switch the theme in Settings → Appearance: the panel follows.
|