Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/prime-message-origin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"datocms-plugin-sdk": patch
---

Faster host → plugin messaging in Chromium. Chrome deliberately slows the deserialization of cross-origin messages of 16 KB or more when `event.data` is read before `event.origin` (it re-deserializes the payload 4-8 times to mask timing), and Penpal 4 reads `data` first. The SDK now registers a capture-phase `message` listener that reads `event.origin` before Penpal runs, which makes every host message take the fast path: a 266 KB ctx went from ~7 ms to ~1 ms per message in Chrome. Firefox and Safari deserialize eagerly and are unaffected.
5 changes: 5 additions & 0 deletions .changeset/raised-form-controls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"datocms-react-ui": patch
---

`TextInput`, `TextareaInput` and `SelectInput` now paint on `--color--surface-raised`, the same token native DatoCMS form controls use. In dark mode they were one step darker than the controls around them.
5 changes: 5 additions & 0 deletions .changeset/root-font-size.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"datocms-plugin-sdk": minor
---

New `ctx.rootFontSize`: the font size of the host's `<html>` element, in CSS pixels. The host steps it with the viewport width (14.4, 15 or 16 px), and the SDK runtime now mirrors it onto the plugin's `<html>`, so `rem` units and every rem-based token (`--font-size-*`, `--spacing-*`) measure the same inside the frame as in the surrounding UI.
4 changes: 2 additions & 2 deletions packages/react-ui/src/Canvas/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,14 @@ export type CanvasProps = {
* collapsible={{ isOpen, onToggle: () => setOpen((v) => !v) }}
* >
* <p>
* One-level tokens that work on any neutral page. The <code>surface</code>, <code>ink</code> and <code>border</code> families cover the page background, body text and dividers; the <code>surface-raised</code> variants belong to the elevated layer used by modals, dropdowns and popovers. The tone-on-neutral inks (<code>ink-danger</code>, <code>ink-warning</code>, <code>ink-success</code>) color text and icons on a neutral surface; inside a toned panel use that context's own ink instead.
* One-level tokens that work on any neutral page. The <code>surface</code>, <code>ink</code> and <code>border</code> families cover the page background, body text and dividers; the <code>surface-raised</code> variants belong to the elevated layer used by form controls, modals, dropdowns and popovers. The tone-on-neutral inks (<code>ink-danger</code>, <code>ink-warning</code>, <code>ink-success</code>) color text and icons on a neutral surface; inside a toned panel use that context's own ink instead.
* </p>
* <Swatches
* tokens={[
* ['--color--surface', 'Page background everything else sits on'],
* ['--color--surface-hover', 'Hovered row inside lists and tables'],
* ['--color--surface-muted', 'Background of muted section panels and quiet cards'],
* ['--color--surface-raised', 'Elevated layer for modals, dropdowns and popovers'],
* ['--color--surface-raised', 'Elevated layer for form controls, modals, dropdowns and popovers'],
* ['--color--surface-raised-hover', 'Hovered option inside a dropdown menu'],
* ['--color--surface-raised-active', 'Focused or pressed option inside a dropdown menu'],
* ['--color--ink', 'Primary body text'],
Expand Down
2 changes: 1 addition & 1 deletion packages/react-ui/src/Dropdown/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
width: 100%;
border: 1px solid var(--color--border);
appearance: none;
background-color: var(--color--surface);
background-color: var(--color--surface-raised);
background-image: none;
transition: border 0.2s var(--material-ease);
resize: none;
Expand Down
4 changes: 2 additions & 2 deletions packages/react-ui/src/SelectInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const useStyles = (isDisabled?: boolean, error?: boolean) => {
: 'var(--color--focus--border)',
backgroundColor: isDisabled
? 'var(--color--disabled--surface)'
: 'var(--color--surface)',
: 'var(--color--surface-raised)',
boxShadow: `0 0 0 4px ${
error
? 'var(--color--danger-soft--outline)'
Expand All @@ -81,7 +81,7 @@ const useStyles = (isDisabled?: boolean, error?: boolean) => {
: 'var(--color--border)',
backgroundColor: isDisabled
? 'var(--color--disabled--surface)'
: 'var(--color--surface)',
: 'var(--color--surface-raised)',
'&:hover': {
borderColor: error
? 'var(--color--danger-soft--border)'
Expand Down
2 changes: 1 addition & 1 deletion packages/react-ui/src/TextInput/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
border: 1px solid var(--color--border);
appearance: none;
border-radius: 0;
background-color: var(--color--surface);
background-color: var(--color--surface-raised);
background-image: none;
transition: border 0.2s var(--material-ease);
font-size: var(--font-size-m);
Expand Down
2 changes: 1 addition & 1 deletion packages/react-ui/src/TextareaInput/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
border: 1px solid var(--color--border);
appearance: none;
border-radius: 0;
background-color: var(--color--surface);
background-color: var(--color--surface-raised);
background-image: none;
transition: border 0.2s var(--material-ease);
font-size: var(--font-size-m);
Expand Down
58 changes: 34 additions & 24 deletions packages/sdk/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3157,6 +3157,16 @@
"lineNumber": 134
},
"type": "'light' | 'dark'"
},
"rootFontSize": {
"comment": {
"markdownText": "Font size of the host's `<html>` element, in CSS pixels. The host steps\nit with the viewport width, so the SDK runtime mirrors it onto the\nplugin's `<html>`: `rem` units, and with them every `--font-size-*` and\n`--spacing-*` token, then measure the same inside the frame as outside.\n\nTyped as required for parity with the other appearance fields (e.g.\n`colorScheme`), but only hosts new enough to send it populate it: a plugin\nrunning inside an older host reads `undefined` at runtime. Guard for that\nif you consume the value directly."
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 147
},
"type": "number"
}
}
},
Expand All @@ -3172,7 +3182,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 144
"lineNumber": 157
},
"type": "Partial<Record<string, ItemType>>"
},
Expand All @@ -3182,7 +3192,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 151
"lineNumber": 164
},
"type": "Partial<Record<string, Field>>"
},
Expand All @@ -3192,7 +3202,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 158
"lineNumber": 171
},
"type": "Partial<Record<string, Fieldset>>"
},
Expand All @@ -3202,7 +3212,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 165
"lineNumber": 178
},
"type": "Partial<Record<string, User>>"
},
Expand All @@ -3212,7 +3222,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 172
"lineNumber": 185
},
"type": "Partial<Record<string, SsoUser>>"
}
Expand All @@ -3233,7 +3243,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 235
"lineNumber": 248
},
"type": "(itemTypeId: string) => Promise<Field[]>"
},
Expand All @@ -3244,7 +3254,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 254
"lineNumber": 267
},
"type": "(itemTypeId: string) => Promise<Fieldset[]>"
},
Expand All @@ -3255,7 +3265,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 271
"lineNumber": 284
},
"type": "() => Promise<Field[]>"
},
Expand All @@ -3266,7 +3276,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 284
"lineNumber": 297
},
"type": "() => Promise<User[]>"
},
Expand All @@ -3277,7 +3287,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 297
"lineNumber": 310
},
"type": "() => Promise<SsoUser[]>"
}
Expand All @@ -3296,7 +3306,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 319
"lineNumber": 332
},
"type": "(params: Record<string, unknown>) => Promise<void>"
},
Expand All @@ -3307,7 +3317,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 370
"lineNumber": 383
},
"type": "(\n fieldId: string,\n changes: FieldAppearanceChange[],\n ) => Promise<void>"
}
Expand All @@ -3326,7 +3336,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 475
"lineNumber": 488
},
"type": "(message: string) => Promise<void>"
},
Expand All @@ -3337,7 +3347,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 490
"lineNumber": 503
},
"type": "(message: string) => Promise<void>"
},
Expand All @@ -3348,7 +3358,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 514
"lineNumber": 527
},
"type": "<CtaValue = unknown>(\n toast: Toast<CtaValue>,\n ) => Promise<CtaValue | null>"
}
Expand All @@ -3367,7 +3377,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 400
"lineNumber": 413
},
"type": "(itemTypeId: string) => Promise<Item | null>"
},
Expand All @@ -3378,7 +3388,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 421
"lineNumber": 434
},
"type": "{\n (\n itemTypeId: string,\n options: { multiple: true; initialLocationQuery?: ItemListLocationQuery },\n ): Promise<Item[] | null>;\n (\n itemTypeId: string,\n options?: {\n multiple: false;\n initialLocationQuery?: ItemListLocationQuery;\n },\n ): Promise<Item | null>;\n }"
},
Expand All @@ -3389,7 +3399,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 453
"lineNumber": 466
},
"type": "(itemId: string) => Promise<Item | null>"
}
Expand All @@ -3408,7 +3418,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 541
"lineNumber": 554
},
"type": "{\n (options: { multiple: true }): Promise<Upload[] | null>;\n (options?: { multiple: false }): Promise<Upload | null>;\n }"
},
Expand All @@ -3419,7 +3429,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 569
"lineNumber": 582
},
"type": "(\n uploadId: string,\n ) => Promise<(Upload & { deleted?: true }) | null>"
},
Expand All @@ -3430,7 +3440,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 598
"lineNumber": 611
},
"type": "(\n /** The \"single asset\" field structure */\n fileFieldValue: FileFieldValue,\n /** Shows metadata information for a specific locale */\n locale?: string,\n ) => Promise<FileFieldValue | null>"
}
Expand All @@ -3449,7 +3459,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 629
"lineNumber": 642
},
"type": "(modal: Modal) => Promise<unknown>"
},
Expand All @@ -3460,7 +3470,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 666
"lineNumber": 679
},
"type": "(options: ConfirmOptions) => Promise<unknown>"
}
Expand All @@ -3479,7 +3489,7 @@
},
"location": {
"filePath": "src/ctx/base.ts",
"lineNumber": 680
"lineNumber": 693
},
"type": "(path: string) => Promise<void>"
}
Expand Down
54 changes: 49 additions & 5 deletions packages/sdk/src/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,41 @@ export type FullConnectParameters = AssetSourcesHook &
UploadSidebarsHook &
ValidateManualFieldExtensionParametersHook;

function applyColorScheme(properties: unknown): void {
type HostAppearance = {
colorScheme?: 'light' | 'dark';
rootFontSize?: number;
};

/**
* Mirrors the host's appearance onto the plugin's `<html>`: color scheme and
* root font size. Runs on the first settings read and on every `onChange`.
*/
function applyHostAppearance(properties: unknown): void {
if (typeof document === 'undefined') return;
const next = (properties as { colorScheme?: 'light' | 'dark' } | null)
?.colorScheme;
const { colorScheme, rootFontSize } =
(properties as HostAppearance | null) ?? {};
applyColorScheme(colorScheme);
applyRootFontSize(rootFontSize);
}

function applyRootFontSize(next: number | undefined): void {
// Hosts that predate the property leave the plugin's own default in place.
// Reject non-positive/non-finite values too: a host bug or a transient 0
// during boot would otherwise set `font-size: 0px` and collapse every
// rem-based size in the plugin to nothing.
if (typeof next !== 'number' || !Number.isFinite(next) || next <= 0) return;
// The host's <html> font size steps with the viewport width; mirroring it
// makes rem-based sizes (every --font-size-* and --spacing-* token) measure
// the same on both sides of the iframe boundary.
const px = `${next}px`;
// `applyHostAppearance` runs on every ctx update (i.e. every keystroke in a
// sibling field). Writing `fontSize` unconditionally would reflow the whole
// rem-based frame each time, so only touch the DOM when the value changed.
if (document.documentElement.style.fontSize === px) return;
document.documentElement.style.fontSize = px;
}

function applyColorScheme(next: 'light' | 'dark' | undefined): void {
if (next !== 'light' && next !== 'dark') return;
if (document.documentElement.dataset.colorScheme === next) return;
document.documentElement.dataset.colorScheme = next;
Expand Down Expand Up @@ -180,6 +211,18 @@ export async function connect(
),
};

// Chromium deliberately slows the deserialization of cross-origin messages
// of 16 KB or more when `event.data` is read before `event.origin`: it
// re-deserializes the payload 4-8 times in a throwaway isolate to mask
// timing (MaskDeserializationTimings in message_event.cc). Penpal 4 reads
// `data` first. `message` events target `window` directly, so this listener
// runs before Penpal's simply because it is registered first (added before
// connectToParent below); reading `origin` here clears the flag for the
// event, so every message takes the fast path. The value is returned, not
// discarded, so that minifiers with `pure_getters` enabled keep the read.
// No-op where deserialization is eager (Firefox, Safari).
window.addEventListener('message', (event) => event.origin, true);

const penpalConnection = connectToParent({
methods: {
// Protocol revision, not the npm version: it's bumped by hand whenever
Expand All @@ -204,7 +247,7 @@ export async function connect(
),
),
onChange(newSettings: unknown) {
applyColorScheme(newSettings);
applyHostAppearance(newSettings);
if (onChangeListener) {
onChangeListener(newSettings);
}
Expand All @@ -231,8 +274,9 @@ export async function connect(
});

const methods = await penpalConnection.promise;

const initialProperties = await methods.getSettings();
applyColorScheme(initialProperties);
applyHostAppearance(initialProperties);

if (initialProperties.mode === 'onBoot') {
let currentProperties = initialProperties;
Expand Down
Loading
Loading