Skip to content
Merged
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
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## v0.4.0 - 2026.08.28

### Changed

- ⚠️ Migrate the front-end to `@studiometa/js-toolkit` v4 (`^4.0.0-alpha.1`, published on the `next` dist-tag). The shipped shell called `createApp()`, which v4 removed, so any consumer already on v4 got `TypeError: createApp is not a function` before the playground rendered anything. Registration is now `registerComponent(Playground)`, and the page template declares `data-component="Playground"` on `<body>` so an instance exists because its element is in the document. Consumers must be on js-toolkit v4 ([#79](https://github.com/studiometa/playground/pull/79))
- ⚠️ `createPlayground()` no longer returns an application object — v4 has none. It configures the playground and registers the root component ([#79](https://github.com/studiometa/playground/pull/79))
- ⚠️ Coordination between the root component and the editors no longer relies on mount order. v4 gives no ordering guarantee between a parent and its children, so `Playground` watches `EditorVisibility`, `Editors` and `Iframe` with `$watchChildren()` and pushes the editor visibility onto every instance it sees, including the ones that mount after it. `Resizable` watches its `ResizableSync` children the same way, and reaches the nearest one from a cursor with `$closest()` ([#79](https://github.com/studiometa/playground/pull/79))
- ⚠️ `HtmlEditor`, `ScriptEditor` and `StyleEditor` declare their own `config.name`. They used to inherit `Editor`'s, which v4 would have registered under the wrong `data-component` token ([#79](https://github.com/studiometa/playground/pull/79))
- Editing the script now rebuilds the preview's JavaScript realm. v4 registers a component class under its name once and ignores a second registration of the same name, so re-running an edited script in the same realm kept the author's first version of every class — the editor looked dead. The reload button resets the realm for the same reason ([#79](https://github.com/studiometa/playground/pull/79))
- Editors dispose their Monaco instance and drop their theme subscription when they unmount, through the cleanup `mounted()` returns. v3 had no teardown at all ([#79](https://github.com/studiometa/playground/pull/79))

### Fixed

- Clear the `is-resizing` state from the drag service rather than from a `pointerup` bound to the handle. A pointer released away from the handle never delivered that event, so the class stayed and `is-resizing:pointer-events-none` left the preview iframe unclickable ([#79](https://github.com/studiometa/playground/pull/79))
- Register the preview's import map once per document instead of once per rebuild, which reported one console warning per overlapping specifier ([#79](https://github.com/studiometa/playground/pull/79))
- `ResizableSync.set()` writes to `style.width` / `style.height` rather than to non-existent `width` / `height` element properties, so a synchronised panel keeps its measured size ([#79](https://github.com/studiometa/playground/pull/79))

## v0.3.14 - 2026.08.10

### Fixed
Expand Down
60 changes: 16 additions & 44 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@studiometa/playground-root",
"version": "0.3.14",
"version": "0.4.0",
"description": "A packaged web development playground",
"type": "module",
"private": true,
Expand Down
9 changes: 6 additions & 3 deletions packages/demo/meta.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ export default defineWebpackConfig({
syncColorScheme: true,
dependencies: [
{
// Pinned: js-toolkit v4 is published on the `next` dist-tag, so an
// unversioned esm.sh URL would still serve v3.
specifier: '@studiometa/js-toolkit',
version: '4.0.0-alpha.1',
esmSh: { bundle: false },
subpaths: true,
},
Expand All @@ -28,12 +31,12 @@ export default defineWebpackConfig({
html: resolve('./html-loader.ts'),
},
defaults: {
html: '<p class="m-10">hello world</p>',
html: '<p class="m-10" data-component="App">hello world</p>',
style: `html.dark {
color: #fff;
background-color: #222;
}`,
script: `import { Base, createApp } from '@studiometa/js-toolkit';
script: `import { Base, registerComponent } from '@studiometa/js-toolkit';
import { greet, isDefined } from 'demo-lib';

class App extends Base {
Expand All @@ -48,7 +51,7 @@ class App extends Base {
}
}

createApp(App);`,
registerComponent(App);`,
},
}),
],
Expand Down
2 changes: 1 addition & 1 deletion packages/demo/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@studiometa/playground-demo",
"version": "0.3.14",
"version": "0.4.0",
"type": "module",
"private": true,
"scripts": {
Expand Down
6 changes: 3 additions & 3 deletions packages/playground-preview/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,21 @@ For longer code snippets, use `<script>` elements with custom `type` attributes.
```html
<playground-preview height="80vh" theme="dark">
<script type="playground/html">
<div class="flex items-center gap-4">
<div class="flex items-center gap-4" data-component="App">
<h1>Hello World</h1>
<p>Some longer content here...</p>
</div>
</script>

<script type="playground/script">
import { Base, createApp } from '@studiometa/js-toolkit';
import { Base, registerComponent } from '@studiometa/js-toolkit';

class App extends Base {
static config = { name: 'App' };
mounted() { console.log('mounted!'); }
}

export default createApp(App);
registerComponent(App);
</script>

<script type="playground/css">
Expand Down
2 changes: 1 addition & 1 deletion packages/playground-preview/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@studiometa/playground-preview",
"version": "0.3.14",
"version": "0.4.0",
"type": "module",
"description": "A web component to embed @studiometa/playground previews anywhere",
"publishConfig": {
Expand Down
4 changes: 2 additions & 2 deletions packages/playground/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@studiometa/playground",
"version": "0.3.14",
"version": "0.4.0",
"type": "module",
"publishConfig": {
"access": "public"
Expand Down Expand Up @@ -56,7 +56,7 @@
"build:types": "tsc --build tsconfig.build.json"
},
"dependencies": {
"@studiometa/js-toolkit": "^3.4.3",
"@studiometa/js-toolkit": "^4.0.0-alpha.1",
"@studiometa/webpack-config": "^6.3.6",
"@studiometa/webpack-config-preset-prototyping": "^6.3.6",
"@studiometa/webpack-config-preset-tailwindcss-4": "6.3.6",
Expand Down
20 changes: 16 additions & 4 deletions packages/playground/src/front/js/components/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { getMonaco, registerLspOptions, DARK_THEME, LIGHT_THEME } from '../utils
import type { MonacoNamespace } from '../utils/monaco.js';
import { themeIsDark, watchTheme } from '../store/index.js';

export type EditorProps = BaseProps;
export type EditorProps = BaseProps & {
$emits: {
'content-change': { value: string };
};
};

type MonacoEditor = ReturnType<MonacoNamespace['editor']['create']>;

Expand All @@ -19,7 +23,6 @@ export default class Editor extends Base<EditorProps> {
*/
static config: BaseConfig = {
name: 'Editor',
emits: ['content-change'],
};

/**
Expand Down Expand Up @@ -97,15 +100,24 @@ export default class Editor extends Base<EditorProps> {

addJsAutocompletion(this.#monaco.languages);

watchTheme(async () => {
const unwatchTheme = watchTheme(async () => {
this.#monaco.editor.setTheme((await themeIsDark()) ? DARK_THEME : LIGHT_THEME);
});

this.editor.onDidChangeModelContent(
debounce(() => {
this.$emit('content-change', this.editor.getValue());
this.$emit('content-change', { value: this.editor.getValue() });
}, 500),
);

// v4 replaces `destroyed()` with the cleanup `mounted()` returns. The theme
// subscription and the Monaco instance both belong to this mount cycle.
return () => {
unwatchTheme();
this.editor?.dispose();
this.editor = undefined;
model.dispose();
};
}

async getInitialValue() {
Expand Down
17 changes: 13 additions & 4 deletions packages/playground/src/front/js/components/EditorVisibility.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Base } from '@studiometa/js-toolkit';
import type { BaseConfig, BaseProps } from '@studiometa/js-toolkit';
import { domScheduler } from '@studiometa/js-toolkit/utils';

export type EditorVisibilityProps = BaseProps;

Expand All @@ -15,14 +14,24 @@ export default class EditorVisibility extends Base<EditorVisibilityProps> {
name: 'EditorVisibility',
};

/**
* The content type this editor edits, read from the markup.
*
* The coordinator uses it to tell the three editors apart. It is a DOM fact,
* so it answers before anything mounts.
*/
get lang(): string {
return this.$el.dataset.lang ?? '';
}

show() {
domScheduler.write(() => {
this.$write(() => {
this.$el.style.display = '';
});
}

hide() {
domScheduler.write(() => {
this.$write(() => {
this.$el.style.display = 'none';
});
}
Expand All @@ -38,7 +47,7 @@ export default class EditorVisibility extends Base<EditorVisibilityProps> {
return;
}

domScheduler.read(() => {
this.$read(() => {
if (this.$el.style.display === 'none') {
this.show();
} else {
Expand Down
5 changes: 2 additions & 3 deletions packages/playground/src/front/js/components/Editors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Base } from '@studiometa/js-toolkit';
import type { BaseConfig, BaseProps } from '@studiometa/js-toolkit';
import { domScheduler } from '@studiometa/js-toolkit/utils';

export type EditorsProps = BaseProps;

Expand All @@ -16,13 +15,13 @@ export default class Editors extends Base<EditorsProps> {
};

hide() {
domScheduler.write(() => {
this.$write(() => {
this.$el.classList.add('hidden');
});
}

show() {
domScheduler.write(() => {
this.$write(() => {
this.$el.classList.remove('hidden');
});
}
Expand Down
9 changes: 4 additions & 5 deletions packages/playground/src/front/js/components/HeaderSwitcher.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { Base } from '@studiometa/js-toolkit';
import type { BaseConfig, BaseProps } from '@studiometa/js-toolkit';
import { domScheduler } from '@studiometa/js-toolkit/utils';
import { setHeaderVisibility, headerIsVisible } from '../store/header.js';

export interface HeaderSwitcherProps extends BaseProps {
export type HeaderSwitcherProps = BaseProps & {
$refs: {
show: HTMLButtonElement;
hide: HTMLButtonElement;
};
}
};

/**
* HeaderSwitcher class.
Expand Down Expand Up @@ -46,8 +45,8 @@ export default class HeaderSwitcher extends Base<HeaderSwitcherProps> {
this.$refs.show.focus();
}

update(isVisible) {
domScheduler.write(() => {
update(isVisible: boolean) {
this.$write(() => {
this.$refs.hide.classList.toggle('flex', isVisible);
this.$refs.show.classList.toggle('flex', !isVisible);
this.$refs.hide.classList.toggle('hidden', !isVisible);
Expand Down
Loading
Loading