@openchamber/sdk 2.1.0 → 2.2.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/API.md +51 -8
- package/DOCUMENTATION.md +16 -5
- package/README.md +13 -1
- package/dist/contract.d.ts +59 -5
- package/dist/contract.js +2 -0
- package/dist/host-version.d.ts +2 -1
- package/dist/host.d.ts +22 -5
- package/dist/host.js +203 -6
- package/dist/index.d.ts +9 -3
- package/dist/index.js +4 -1
- package/dist/manifest.d.ts +6 -4
- package/dist/manifest.js +4 -4
- package/dist/parse.d.ts +12 -6
- package/dist/parse.js +2 -0
- package/dist/popover-schemas.d.ts +33 -0
- package/dist/popover-schemas.js +74 -0
- package/dist/popover.d.ts +37 -0
- package/dist/popover.js +57 -0
- package/dist/protocol.d.ts +232 -4
- package/dist/protocol.js +75 -1
- package/dist/schemas.d.ts +2 -0
- package/dist/schemas.js +2 -0
- package/dist/shells.d.ts +69 -0
- package/dist/shells.js +4 -0
- package/dist/status-control-schemas.d.ts +21 -0
- package/dist/status-control-schemas.js +29 -0
- package/dist/status-controls.d.ts +31 -0
- package/dist/status-controls.js +48 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/popover-anchor.d.ts +16 -0
- package/dist/ui/popover-anchor.js +198 -0
- package/dist/ui/theme.js +15 -0
- package/dist/workspace-schemas.d.ts +16 -0
- package/dist/workspace-schemas.js +4 -4
- package/dist/workspace.d.ts +10 -4
- package/dist/workspace.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const guestStatusControlsSchema: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
3
|
+
kind: z.ZodLiteral<"button">;
|
|
4
|
+
id: z.ZodString;
|
|
5
|
+
label: z.ZodString;
|
|
6
|
+
disabled: z.ZodOptional<z.ZodBoolean>;
|
|
7
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
8
|
+
kind: z.ZodLiteral<"select">;
|
|
9
|
+
id: z.ZodString;
|
|
10
|
+
label: z.ZodString;
|
|
11
|
+
value: z.ZodString;
|
|
12
|
+
options: z.ZodArray<z.ZodObject<{
|
|
13
|
+
value: z.ZodString;
|
|
14
|
+
label: z.ZodString;
|
|
15
|
+
}, z.core.$strict>>;
|
|
16
|
+
disabled: z.ZodOptional<z.ZodBoolean>;
|
|
17
|
+
}, z.core.$strict>], "kind">>;
|
|
18
|
+
export declare const guestStatusControlEventSchema: z.ZodObject<{
|
|
19
|
+
id: z.ZodString;
|
|
20
|
+
value: z.ZodOptional<z.ZodString>;
|
|
21
|
+
}, z.core.$strict>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { GUEST_STATUS_CONTROL_ID, GUEST_STATUS_CONTROL_LABEL_MAX, GUEST_STATUS_CONTROL_OPTIONS_MAX, GUEST_STATUS_CONTROL_VALUE_MAX, GUEST_STATUS_CONTROLS_MAX, } from "./status-controls.js";
|
|
3
|
+
const idSchema = z.string().regex(GUEST_STATUS_CONTROL_ID);
|
|
4
|
+
const labelSchema = z.string().min(1).max(GUEST_STATUS_CONTROL_LABEL_MAX);
|
|
5
|
+
const optionSchema = z.object({
|
|
6
|
+
value: z.string().min(1).max(GUEST_STATUS_CONTROL_VALUE_MAX),
|
|
7
|
+
label: labelSchema,
|
|
8
|
+
}).strict();
|
|
9
|
+
const buttonSchema = z.object({
|
|
10
|
+
kind: z.literal('button'),
|
|
11
|
+
id: idSchema,
|
|
12
|
+
label: labelSchema,
|
|
13
|
+
disabled: z.boolean().optional(),
|
|
14
|
+
}).strict();
|
|
15
|
+
const selectSchema = z.object({
|
|
16
|
+
kind: z.literal('select'),
|
|
17
|
+
id: idSchema,
|
|
18
|
+
label: labelSchema,
|
|
19
|
+
value: z.string().min(1).max(GUEST_STATUS_CONTROL_VALUE_MAX),
|
|
20
|
+
options: z.array(optionSchema).min(1).max(GUEST_STATUS_CONTROL_OPTIONS_MAX),
|
|
21
|
+
disabled: z.boolean().optional(),
|
|
22
|
+
}).strict().refine((control) => new Set(control.options.map((option) => option.value)).size === control.options.length, { message: 'status control option values must be unique', path: ['options'] }).refine((control) => control.options.some((option) => option.value === control.value), { message: 'status control value must be one of its options', path: ['value'] });
|
|
23
|
+
export const guestStatusControlsSchema = z.array(z.discriminatedUnion('kind', [buttonSchema, selectSchema]))
|
|
24
|
+
.max(GUEST_STATUS_CONTROLS_MAX)
|
|
25
|
+
.refine((controls) => new Set(controls.map((control) => control.id)).size === controls.length, { message: 'status control ids must be unique' });
|
|
26
|
+
export const guestStatusControlEventSchema = z.object({
|
|
27
|
+
id: idSchema,
|
|
28
|
+
value: z.string().min(1).max(GUEST_STATUS_CONTROL_VALUE_MAX).optional(),
|
|
29
|
+
}).strict();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const GUEST_STATUS_CONTROLS_MAX = 4;
|
|
2
|
+
export declare const GUEST_STATUS_CONTROL_ID: RegExp;
|
|
3
|
+
export declare const GUEST_STATUS_CONTROL_LABEL_MAX = 60;
|
|
4
|
+
export declare const GUEST_STATUS_CONTROL_OPTIONS_MAX = 16;
|
|
5
|
+
export declare const GUEST_STATUS_CONTROL_VALUE_MAX = 80;
|
|
6
|
+
export type GuestStatusControlOption = {
|
|
7
|
+
value: string;
|
|
8
|
+
label: string;
|
|
9
|
+
};
|
|
10
|
+
export type GuestStatusControl = {
|
|
11
|
+
kind: 'button';
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'select';
|
|
17
|
+
id: string;
|
|
18
|
+
label: string;
|
|
19
|
+
value: string;
|
|
20
|
+
options: GuestStatusControlOption[];
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
};
|
|
23
|
+
export type GuestStatusControlEvent = {
|
|
24
|
+
id: string;
|
|
25
|
+
value?: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Guest-side fast-fail guard for `setStatusControls`. The host zod schema is
|
|
29
|
+
* still authoritative because JavaScript callers can bypass this SDK method.
|
|
30
|
+
*/
|
|
31
|
+
export declare const isGuestStatusControls: (controls: GuestStatusControl[]) => boolean;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const GUEST_STATUS_CONTROLS_MAX = 4;
|
|
2
|
+
export const GUEST_STATUS_CONTROL_ID = /^[a-z][a-z0-9-]{0,39}$/;
|
|
3
|
+
export const GUEST_STATUS_CONTROL_LABEL_MAX = 60;
|
|
4
|
+
export const GUEST_STATUS_CONTROL_OPTIONS_MAX = 16;
|
|
5
|
+
export const GUEST_STATUS_CONTROL_VALUE_MAX = 80;
|
|
6
|
+
const isControlObject = (value) => (Object(value) === value && !Array.isArray(value));
|
|
7
|
+
const hasOnlyKeys = (value, keys) => Object.keys(value).every((key) => keys.includes(key));
|
|
8
|
+
const isLabel = (value) => (String(value) === value && value.length >= 1 && value.length <= GUEST_STATUS_CONTROL_LABEL_MAX);
|
|
9
|
+
const isValue = (value) => (String(value) === value && value.length >= 1 && value.length <= GUEST_STATUS_CONTROL_VALUE_MAX);
|
|
10
|
+
/**
|
|
11
|
+
* Guest-side fast-fail guard for `setStatusControls`. The host zod schema is
|
|
12
|
+
* still authoritative because JavaScript callers can bypass this SDK method.
|
|
13
|
+
*/
|
|
14
|
+
export const isGuestStatusControls = (controls) => {
|
|
15
|
+
// The public type is precise, but this boundary also receives untyped JavaScript callers.
|
|
16
|
+
if (!Array.isArray(controls) || controls.length > GUEST_STATUS_CONTROLS_MAX)
|
|
17
|
+
return false;
|
|
18
|
+
const ids = new Set();
|
|
19
|
+
for (const control of controls) {
|
|
20
|
+
if (!isControlObject(control) || !GUEST_STATUS_CONTROL_ID.test(control.id) || !isLabel(control.label))
|
|
21
|
+
return false;
|
|
22
|
+
if (control.disabled !== undefined && control.disabled !== true && control.disabled !== false)
|
|
23
|
+
return false;
|
|
24
|
+
if (ids.has(control.id))
|
|
25
|
+
return false;
|
|
26
|
+
ids.add(control.id);
|
|
27
|
+
if (control.kind === 'button') {
|
|
28
|
+
if (!hasOnlyKeys(control, ['kind', 'id', 'label', 'disabled']))
|
|
29
|
+
return false;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (control.kind !== 'select' || !hasOnlyKeys(control, ['kind', 'id', 'label', 'value', 'options', 'disabled']))
|
|
33
|
+
return false;
|
|
34
|
+
if (!isValue(control.value) || !Array.isArray(control.options)
|
|
35
|
+
|| control.options.length < 1 || control.options.length > GUEST_STATUS_CONTROL_OPTIONS_MAX)
|
|
36
|
+
return false;
|
|
37
|
+
const values = new Set();
|
|
38
|
+
for (const option of control.options) {
|
|
39
|
+
if (!isControlObject(option) || !hasOnlyKeys(option, ['value', 'label'])
|
|
40
|
+
|| !isValue(option.value) || !isLabel(option.label) || values.has(option.value))
|
|
41
|
+
return false;
|
|
42
|
+
values.add(option.value);
|
|
43
|
+
}
|
|
44
|
+
if (!values.has(control.value))
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
};
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -33,3 +33,5 @@ export { mountMenu } from './menu.ts';
|
|
|
33
33
|
export type { MenuHandle, MenuItem, MenuProps } from './menu.ts';
|
|
34
34
|
export { mountText, splitTextMedia } from './text.ts';
|
|
35
35
|
export type { TextHandle, TextPart, TextProps } from './text.ts';
|
|
36
|
+
export { mountPopoverAnchor } from './popover-anchor.ts';
|
|
37
|
+
export type { PopoverAnchorHandle, PopoverAnchorOptions } from './popover-anchor.ts';
|
package/dist/ui/index.js
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HostClient } from '../host.ts';
|
|
2
|
+
import type { GuestPopoverSide } from '../popover.ts';
|
|
3
|
+
import type { JsonValue } from '../contract.ts';
|
|
4
|
+
export type PopoverAnchorOptions = {
|
|
5
|
+
host: Pick<HostClient, 'openPopover' | 'closePopover' | 'setPopoverAnchorActive' | 'onPopoverClosed'>;
|
|
6
|
+
getData: () => JsonValue;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
side?: GuestPopoverSide;
|
|
10
|
+
label?: string;
|
|
11
|
+
};
|
|
12
|
+
export type PopoverAnchorHandle = {
|
|
13
|
+
dispose: () => void;
|
|
14
|
+
};
|
|
15
|
+
/** Adds a bounded host popover preview to an extension-owned element. */
|
|
16
|
+
export declare const mountPopoverAnchor: (element: HTMLElement, options: PopoverAnchorOptions) => PopoverAnchorHandle;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
let nextPopoverAnchorId = 0;
|
|
2
|
+
const isKeyboardEvent = (event) => 'key' in event;
|
|
3
|
+
/** Adds a bounded host popover preview to an extension-owned element. */
|
|
4
|
+
export const mountPopoverAnchor = (element, options) => {
|
|
5
|
+
const document = element.ownerDocument;
|
|
6
|
+
const window = document.defaultView;
|
|
7
|
+
let timer = null;
|
|
8
|
+
let activeId = null;
|
|
9
|
+
let disposed = false;
|
|
10
|
+
let ignoreNextFocus = false;
|
|
11
|
+
let pointerActive = false;
|
|
12
|
+
let focusActive = false;
|
|
13
|
+
let observer = null;
|
|
14
|
+
const previousExpanded = element.getAttribute('aria-expanded');
|
|
15
|
+
const previousPopup = element.getAttribute('aria-haspopup');
|
|
16
|
+
const previousLabel = element.getAttribute('aria-label');
|
|
17
|
+
const ownsExpanded = previousExpanded === null;
|
|
18
|
+
const clearTimer = () => {
|
|
19
|
+
if (timer !== null)
|
|
20
|
+
clearTimeout(timer);
|
|
21
|
+
timer = null;
|
|
22
|
+
};
|
|
23
|
+
const removeActiveListeners = () => {
|
|
24
|
+
document.removeEventListener('scroll', closeForAnchor, true);
|
|
25
|
+
document.removeEventListener('pointerdown', closeForOutside, true);
|
|
26
|
+
window?.removeEventListener('resize', closeForAnchor);
|
|
27
|
+
observer?.disconnect();
|
|
28
|
+
observer = null;
|
|
29
|
+
};
|
|
30
|
+
const close = (reason = 'closed', restoreFocus = false) => {
|
|
31
|
+
const id = activeId;
|
|
32
|
+
clearTimer();
|
|
33
|
+
activeId = null;
|
|
34
|
+
removeActiveListeners();
|
|
35
|
+
element.removeAttribute('data-oc-popover-open');
|
|
36
|
+
if (ownsExpanded)
|
|
37
|
+
element.setAttribute('aria-expanded', 'false');
|
|
38
|
+
if (id)
|
|
39
|
+
void options.host.closePopover(id, reason).catch(() => undefined);
|
|
40
|
+
if (restoreFocus) {
|
|
41
|
+
ignoreNextFocus = true;
|
|
42
|
+
element.focus();
|
|
43
|
+
queueMicrotask(() => { ignoreNextFocus = false; });
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const closeForAnchor = () => close();
|
|
47
|
+
const closeForOutside = (event) => {
|
|
48
|
+
const target = event.target;
|
|
49
|
+
// SAFETY: DOM pointer events target a Node; this avoids cross-realm constructors.
|
|
50
|
+
if (target !== element && !element.contains(target))
|
|
51
|
+
close();
|
|
52
|
+
};
|
|
53
|
+
const watchActive = () => {
|
|
54
|
+
document.addEventListener('scroll', closeForAnchor, true);
|
|
55
|
+
document.addEventListener('pointerdown', closeForOutside, true);
|
|
56
|
+
window?.addEventListener('resize', closeForAnchor);
|
|
57
|
+
observer = new MutationObserver(() => { if (!document.contains(element))
|
|
58
|
+
close(); });
|
|
59
|
+
observer.observe(document, { childList: true, subtree: true });
|
|
60
|
+
};
|
|
61
|
+
const reportAnchorActivity = () => {
|
|
62
|
+
if (activeId)
|
|
63
|
+
void options.host.setPopoverAnchorActive(activeId, pointerActive || focusActive).catch(() => undefined);
|
|
64
|
+
};
|
|
65
|
+
const open = (focus) => {
|
|
66
|
+
clearTimer();
|
|
67
|
+
if (disposed || activeId || !element.isConnected)
|
|
68
|
+
return;
|
|
69
|
+
const rect = element.getBoundingClientRect();
|
|
70
|
+
if (rect.width <= 0 || rect.height <= 0)
|
|
71
|
+
return;
|
|
72
|
+
const id = `oc-popover-${++nextPopoverAnchorId}`;
|
|
73
|
+
let data;
|
|
74
|
+
try {
|
|
75
|
+
data = options.getData();
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
activeId = id;
|
|
81
|
+
element.setAttribute('data-oc-popover-open', 'true');
|
|
82
|
+
if (ownsExpanded)
|
|
83
|
+
element.setAttribute('aria-expanded', 'true');
|
|
84
|
+
watchActive();
|
|
85
|
+
void options.host.openPopover({
|
|
86
|
+
id,
|
|
87
|
+
anchor: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
|
88
|
+
width: options.width,
|
|
89
|
+
height: options.height,
|
|
90
|
+
side: options.side,
|
|
91
|
+
focus,
|
|
92
|
+
data,
|
|
93
|
+
}).catch(() => {
|
|
94
|
+
if (activeId === id)
|
|
95
|
+
close();
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
const scheduleOpen = () => {
|
|
99
|
+
if (!activeId && !disposed && timer === null)
|
|
100
|
+
timer = setTimeout(() => open(false), 250);
|
|
101
|
+
};
|
|
102
|
+
const onPointerEnter = (event) => {
|
|
103
|
+
if ('pointerType' in event && event.pointerType && event.pointerType !== 'mouse')
|
|
104
|
+
return;
|
|
105
|
+
pointerActive = true;
|
|
106
|
+
if (activeId)
|
|
107
|
+
reportAnchorActivity();
|
|
108
|
+
else if (!ignoreNextFocus)
|
|
109
|
+
scheduleOpen();
|
|
110
|
+
};
|
|
111
|
+
const onPointerLeave = (event) => {
|
|
112
|
+
if ('pointerType' in event && event.pointerType && event.pointerType !== 'mouse')
|
|
113
|
+
return;
|
|
114
|
+
pointerActive = false;
|
|
115
|
+
clearTimer();
|
|
116
|
+
reportAnchorActivity();
|
|
117
|
+
};
|
|
118
|
+
const onFocus = () => {
|
|
119
|
+
focusActive = true;
|
|
120
|
+
if (activeId)
|
|
121
|
+
reportAnchorActivity();
|
|
122
|
+
else if (!ignoreNextFocus && element.matches(':focus-visible'))
|
|
123
|
+
open(false);
|
|
124
|
+
};
|
|
125
|
+
const onFocusOut = () => {
|
|
126
|
+
focusActive = false;
|
|
127
|
+
reportAnchorActivity();
|
|
128
|
+
};
|
|
129
|
+
const onKeyDown = (event) => {
|
|
130
|
+
if (!isKeyboardEvent(event))
|
|
131
|
+
return;
|
|
132
|
+
if (event.key === 'ArrowRight') {
|
|
133
|
+
event.preventDefault();
|
|
134
|
+
if (activeId)
|
|
135
|
+
close();
|
|
136
|
+
open(true);
|
|
137
|
+
}
|
|
138
|
+
else if (event.key === 'Escape' && activeId) {
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
close('escape', true);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const unsubscribe = options.host.onPopoverClosed((event) => {
|
|
144
|
+
if (event.id !== activeId)
|
|
145
|
+
return;
|
|
146
|
+
activeId = null;
|
|
147
|
+
clearTimer();
|
|
148
|
+
removeActiveListeners();
|
|
149
|
+
element.removeAttribute('data-oc-popover-open');
|
|
150
|
+
if (ownsExpanded)
|
|
151
|
+
element.setAttribute('aria-expanded', 'false');
|
|
152
|
+
if (event.reason === 'escape') {
|
|
153
|
+
ignoreNextFocus = true;
|
|
154
|
+
element.focus();
|
|
155
|
+
queueMicrotask(() => { ignoreNextFocus = false; });
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
element.setAttribute('aria-haspopup', 'dialog');
|
|
159
|
+
if (ownsExpanded)
|
|
160
|
+
element.setAttribute('aria-expanded', 'false');
|
|
161
|
+
if (options.label)
|
|
162
|
+
element.setAttribute('aria-label', options.label);
|
|
163
|
+
element.addEventListener('pointerenter', onPointerEnter);
|
|
164
|
+
// Crossing opaque iframe documents can omit an enter transition. A real
|
|
165
|
+
// pointer move over this element still establishes a new hover intent.
|
|
166
|
+
element.addEventListener('pointermove', onPointerEnter);
|
|
167
|
+
element.addEventListener('pointerleave', onPointerLeave);
|
|
168
|
+
element.addEventListener('focus', onFocus);
|
|
169
|
+
element.addEventListener('focusout', onFocusOut);
|
|
170
|
+
element.addEventListener('keydown', onKeyDown);
|
|
171
|
+
return { dispose: () => {
|
|
172
|
+
if (disposed)
|
|
173
|
+
return;
|
|
174
|
+
disposed = true;
|
|
175
|
+
clearTimer();
|
|
176
|
+
close();
|
|
177
|
+
unsubscribe();
|
|
178
|
+
element.removeAttribute('data-oc-popover-open');
|
|
179
|
+
if (previousExpanded === null)
|
|
180
|
+
element.removeAttribute('aria-expanded');
|
|
181
|
+
if (previousPopup === null)
|
|
182
|
+
element.removeAttribute('aria-haspopup');
|
|
183
|
+
else
|
|
184
|
+
element.setAttribute('aria-haspopup', previousPopup);
|
|
185
|
+
if (options.label) {
|
|
186
|
+
if (previousLabel === null)
|
|
187
|
+
element.removeAttribute('aria-label');
|
|
188
|
+
else
|
|
189
|
+
element.setAttribute('aria-label', previousLabel);
|
|
190
|
+
}
|
|
191
|
+
element.removeEventListener('pointerenter', onPointerEnter);
|
|
192
|
+
element.removeEventListener('pointermove', onPointerEnter);
|
|
193
|
+
element.removeEventListener('pointerleave', onPointerLeave);
|
|
194
|
+
element.removeEventListener('focus', onFocus);
|
|
195
|
+
element.removeEventListener('focusout', onFocusOut);
|
|
196
|
+
element.removeEventListener('keydown', onKeyDown);
|
|
197
|
+
} };
|
|
198
|
+
};
|
package/dist/ui/theme.js
CHANGED
|
@@ -54,6 +54,16 @@ const TOKEN_VARS = [
|
|
|
54
54
|
['--font-mono', 'mono'],
|
|
55
55
|
['--radius', 'radius'],
|
|
56
56
|
];
|
|
57
|
+
const SYNTAX_TOKEN_VARS = [
|
|
58
|
+
['--oc-syntax-keyword', '--syntax-keyword', 'syntaxKeyword'],
|
|
59
|
+
['--oc-syntax-string', '--syntax-string', 'syntaxString'],
|
|
60
|
+
['--oc-syntax-number', '--syntax-number', 'syntaxNumber'],
|
|
61
|
+
['--oc-syntax-function', '--syntax-function', 'syntaxFunction'],
|
|
62
|
+
['--oc-syntax-type', '--syntax-type', 'syntaxType'],
|
|
63
|
+
['--oc-syntax-comment', '--syntax-comment', 'syntaxComment'],
|
|
64
|
+
['--oc-syntax-variable', '--syntax-variable', 'syntaxVariable'],
|
|
65
|
+
['--oc-syntax-operator', '--syntax-operator', 'syntaxOperator'],
|
|
66
|
+
];
|
|
57
67
|
/**
|
|
58
68
|
* Paint the host theme onto the iframe root. Guest chrome reads these
|
|
59
69
|
* variables, and the root itself gets the host font and text colour so plain
|
|
@@ -65,6 +75,11 @@ export const applyHostTheme = (theme, root) => {
|
|
|
65
75
|
for (const [name, key] of TOKEN_VARS) {
|
|
66
76
|
root.style.setProperty(name, theme.tokens[key]);
|
|
67
77
|
}
|
|
78
|
+
for (const [alias, hostName, key] of SYNTAX_TOKEN_VARS) {
|
|
79
|
+
const value = theme.tokens[key] ?? '';
|
|
80
|
+
root.style.setProperty(alias, value);
|
|
81
|
+
root.style.setProperty(hostName, value);
|
|
82
|
+
}
|
|
68
83
|
root.style.setProperty('font-family', theme.tokens.font);
|
|
69
84
|
root.style.setProperty('font-size', '0.875rem');
|
|
70
85
|
root.style.setProperty('line-height', '1.45');
|
|
@@ -104,15 +104,31 @@ export declare const guestSessionWorktreeSchema: z.ZodUnion<readonly [z.ZodBoole
|
|
|
104
104
|
export declare const guestStorageRequestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
105
105
|
op: z.ZodLiteral<"get">;
|
|
106
106
|
key: z.ZodString;
|
|
107
|
+
scope: z.ZodOptional<z.ZodEnum<{
|
|
108
|
+
instance: "instance";
|
|
109
|
+
device: "device";
|
|
110
|
+
}>>;
|
|
107
111
|
}, z.core.$strict>, z.ZodObject<{
|
|
108
112
|
op: z.ZodLiteral<"delete">;
|
|
109
113
|
key: z.ZodString;
|
|
114
|
+
scope: z.ZodOptional<z.ZodEnum<{
|
|
115
|
+
instance: "instance";
|
|
116
|
+
device: "device";
|
|
117
|
+
}>>;
|
|
110
118
|
}, z.core.$strict>, z.ZodObject<{
|
|
111
119
|
op: z.ZodLiteral<"set">;
|
|
112
120
|
key: z.ZodString;
|
|
113
121
|
value: z.ZodJSONSchema;
|
|
122
|
+
scope: z.ZodOptional<z.ZodEnum<{
|
|
123
|
+
instance: "instance";
|
|
124
|
+
device: "device";
|
|
125
|
+
}>>;
|
|
114
126
|
}, z.core.$strict>, z.ZodObject<{
|
|
115
127
|
op: z.ZodLiteral<"keys">;
|
|
128
|
+
scope: z.ZodOptional<z.ZodEnum<{
|
|
129
|
+
instance: "instance";
|
|
130
|
+
device: "device";
|
|
131
|
+
}>>;
|
|
116
132
|
}, z.core.$strict>], "op">;
|
|
117
133
|
export declare const guestStorageResultSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
118
134
|
storage: z.ZodLiteral<true>;
|
|
@@ -31,10 +31,10 @@ export const guestSessionWorktreeSchema = z.union([
|
|
|
31
31
|
]);
|
|
32
32
|
const storageKey = z.string().min(1).max(GUEST_STORAGE_KEY_MAX);
|
|
33
33
|
export const guestStorageRequestSchema = z.discriminatedUnion('op', [
|
|
34
|
-
z.object({ op: z.literal('get'), key: storageKey }).strict(),
|
|
35
|
-
z.object({ op: z.literal('delete'), key: storageKey }).strict(),
|
|
36
|
-
z.object({ op: z.literal('set'), key: storageKey, value: z.json().refine((value) => new TextEncoder().encode(JSON.stringify(value)).length <= GUEST_STORAGE_VALUE_BYTES) }).strict(),
|
|
37
|
-
z.object({ op: z.literal('keys') }).strict(),
|
|
34
|
+
z.object({ op: z.literal('get'), key: storageKey, scope: z.enum(['instance', 'device']).optional() }).strict(),
|
|
35
|
+
z.object({ op: z.literal('delete'), key: storageKey, scope: z.enum(['instance', 'device']).optional() }).strict(),
|
|
36
|
+
z.object({ op: z.literal('set'), key: storageKey, value: z.json().refine((value) => new TextEncoder().encode(JSON.stringify(value)).length <= GUEST_STORAGE_VALUE_BYTES), scope: z.enum(['instance', 'device']).optional() }).strict(),
|
|
37
|
+
z.object({ op: z.literal('keys'), scope: z.enum(['instance', 'device']).optional() }).strict(),
|
|
38
38
|
]);
|
|
39
39
|
export const guestStorageResultSchema = z.union([
|
|
40
40
|
z.object({ storage: z.literal(true), op: z.literal('get'), found: z.literal(false) }),
|
package/dist/workspace.d.ts
CHANGED
|
@@ -74,17 +74,23 @@ export type GuestWorkspaceUpdate = {
|
|
|
74
74
|
export declare const GUEST_STORAGE_KEY_MAX = 128;
|
|
75
75
|
export declare const GUEST_STORAGE_VALUE_BYTES = 65536;
|
|
76
76
|
export declare const GUEST_STORAGE_TOTAL_BYTES = 2097152;
|
|
77
|
+
/** Maximum bytes retained across all device-scoped guest storage namespaces. */
|
|
78
|
+
export declare const GUEST_DEVICE_STORAGE_TOTAL_BYTES = 16777216;
|
|
77
79
|
export declare const GUEST_STORAGE_KEYS_MAX = 2000;
|
|
78
|
-
export type
|
|
80
|
+
export type GuestStorageScope = 'instance' | 'device';
|
|
81
|
+
export type GuestStorageOptions = {
|
|
82
|
+
scope?: GuestStorageScope;
|
|
83
|
+
};
|
|
84
|
+
export type GuestStorageRequest = ({
|
|
79
85
|
op: 'get' | 'delete';
|
|
80
86
|
key: string;
|
|
81
|
-
} | {
|
|
87
|
+
} & GuestStorageOptions) | ({
|
|
82
88
|
op: 'set';
|
|
83
89
|
key: string;
|
|
84
90
|
value: JsonValue;
|
|
85
|
-
} | {
|
|
91
|
+
} & GuestStorageOptions) | ({
|
|
86
92
|
op: 'keys';
|
|
87
|
-
};
|
|
93
|
+
} & GuestStorageOptions);
|
|
88
94
|
export type GuestStorageResult = {
|
|
89
95
|
storage: true;
|
|
90
96
|
} & ({
|
package/dist/workspace.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export const GUEST_STORAGE_KEY_MAX = 128;
|
|
2
2
|
export const GUEST_STORAGE_VALUE_BYTES = 65_536;
|
|
3
3
|
export const GUEST_STORAGE_TOTAL_BYTES = 2_097_152;
|
|
4
|
+
/** Maximum bytes retained across all device-scoped guest storage namespaces. */
|
|
5
|
+
export const GUEST_DEVICE_STORAGE_TOTAL_BYTES = 16_777_216;
|
|
4
6
|
export const GUEST_STORAGE_KEYS_MAX = 2_000;
|