@nonoun/native-playground 0.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.
@@ -0,0 +1,17 @@
1
+ import { EditorView } from '@codemirror/view';
2
+ export type TabName = 'html' | 'css' | 'js';
3
+ export interface EditorInstance {
4
+ view: EditorView;
5
+ getCode(): string;
6
+ setCode(code: string): void;
7
+ destroy(): void;
8
+ }
9
+ export interface EditorOptions {
10
+ parent: HTMLElement;
11
+ initialCode: string;
12
+ language: TabName;
13
+ readonly?: boolean;
14
+ onChange?: (code: string) => void;
15
+ }
16
+ export declare function createEditor(options: EditorOptions): EditorInstance;
17
+ //# sourceMappingURL=editors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editors.d.ts","sourceRoot":"","sources":["../src/editors.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAM9C,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC;AAE5C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,IAAI,MAAM,CAAC;IAClB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAcD,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc,CA2CnE"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Builds an HTML string for use as iframe `srcdoc` in the playground.
3
+ * Renders user code (HTML, CSS, JS) in an isolated iframe with
4
+ * native-ui loaded via external CSS and register script URLs.
5
+ */
6
+ export interface SrcdocOptions {
7
+ /** User's HTML from the HTML pane */
8
+ html: string;
9
+ /** User's CSS from the CSS pane */
10
+ css: string;
11
+ /** User's JS from the JS pane */
12
+ js: string;
13
+ /** URL to native-ui.css (CDN or bundled) */
14
+ cssUrl: string;
15
+ /** URL to native-ui register script (CDN or bundled) */
16
+ registerUrl: string;
17
+ /** Optional OKLCH token overrides as CSS custom properties */
18
+ themeOverrides?: string;
19
+ }
20
+ export declare function buildSrcdoc(options: SrcdocOptions): string;
21
+ //# sourceMappingURL=template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/iframe/template.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,iCAAiC;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAUD,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAoD1D"}
@@ -0,0 +1,8 @@
1
+ export { NuiPlayground } from './nui-playground-element.ts';
2
+ export { createPlaygroundStore } from './playground-store.ts';
3
+ export type { PlaygroundStore, ConsoleEntry } from './playground-store.ts';
4
+ export { createEditor } from './editors.ts';
5
+ export type { EditorInstance, EditorOptions, TabName } from './editors.ts';
6
+ export { buildSrcdoc } from './iframe/template.ts';
7
+ export type { SrcdocOptions } from './iframe/template.ts';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAG5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG3E,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG3E,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,398 @@
1
+ import { CopyController as e, UIElement as t, signal as n } from "@nonoun/native-ui";
2
+ import { EditorState as r } from "@codemirror/state";
3
+ import { EditorView as i } from "@codemirror/view";
4
+ import { nuiBaseExtensions as a, nuiSyntaxHighlighting as o, nuiTheme as s } from "@nonoun/nui-codemirror";
5
+ import { html as c } from "@codemirror/lang-html";
6
+ import { css as l } from "@codemirror/lang-css";
7
+ import { javascript as u } from "@codemirror/lang-javascript";
8
+ function d(e, t, r) {
9
+ return {
10
+ html: n(e),
11
+ css: n(t),
12
+ js: n(r),
13
+ initialHtml: e,
14
+ initialCss: t,
15
+ initialJs: r,
16
+ activeTab: n("html"),
17
+ consoleOpen: n(!1),
18
+ fullscreen: n(!1),
19
+ orientation: n("auto"),
20
+ autoRun: n(!0),
21
+ debounce: n(300),
22
+ readonly: n(!1),
23
+ consoleEntries: n([]),
24
+ previewTheme: n("")
25
+ };
26
+ }
27
+ function f(e) {
28
+ switch (e) {
29
+ case "html": return c();
30
+ case "css": return l();
31
+ case "js": return u();
32
+ }
33
+ }
34
+ function p(e) {
35
+ let { parent: t, initialCode: n, language: c, readonly: l = !1, onChange: u } = e, d = [
36
+ s,
37
+ o,
38
+ a,
39
+ f(c),
40
+ r.readOnly.of(l)
41
+ ];
42
+ u && d.push(i.updateListener.of((e) => {
43
+ e.docChanged && u(e.state.doc.toString());
44
+ }));
45
+ let p = new i({
46
+ state: r.create({
47
+ doc: n,
48
+ extensions: d
49
+ }),
50
+ parent: t
51
+ });
52
+ return {
53
+ view: p,
54
+ getCode() {
55
+ return p.state.doc.toString();
56
+ },
57
+ setCode(e) {
58
+ p.dispatch({ changes: {
59
+ from: 0,
60
+ to: p.state.doc.length,
61
+ insert: e
62
+ } });
63
+ },
64
+ destroy() {
65
+ p.destroy();
66
+ }
67
+ };
68
+ }
69
+ /**
70
+ * Escape `<\/script>` sequences in user-provided JS to prevent
71
+ * breaking out of the inline script tag.
72
+ */
73
+ function m(e) {
74
+ return e.replace(/<\/script>/gi, "<\\/script>");
75
+ }
76
+ function h(e) {
77
+ let { html: t, css: n, js: r, cssUrl: i, registerUrl: a, themeOverrides: o } = e;
78
+ return `<!DOCTYPE html>
79
+ <html>
80
+ <head>
81
+ <meta charset="utf-8">
82
+ <meta name="viewport" content="width=device-width, initial-scale=1">
83
+ <link rel="stylesheet" href="${i}">${o ? `\n <style id="theme-overrides">:root { ${o} }</style>` : ""}
84
+ <style id="user-css">${n}</style>
85
+ </head>
86
+ <body>
87
+ ${t}
88
+ <script src="${a}" type="module"><\/script>
89
+ <script>
90
+ (function() {
91
+ const _log = console.log;
92
+ const _warn = console.warn;
93
+ const _error = console.error;
94
+ const _clear = console.clear;
95
+ function send(level, args) {
96
+ window.parent.postMessage({
97
+ type: 'playground:console',
98
+ level: level,
99
+ args: args.map(function(a) {
100
+ try { return typeof a === 'object' ? JSON.stringify(a) : String(a); }
101
+ catch (_e) { return String(a); }
102
+ })
103
+ }, '*');
104
+ }
105
+ console.log = function() { var args = [].slice.call(arguments); _log.apply(console, args); send('log', args); };
106
+ console.warn = function() { var args = [].slice.call(arguments); _warn.apply(console, args); send('warn', args); };
107
+ console.error = function() { var args = [].slice.call(arguments); _error.apply(console, args); send('error', args); };
108
+ console.clear = function() { _clear.call(console); send('clear', []); };
109
+ window.addEventListener('error', function(e) {
110
+ send('error', [e.message + ' (' + e.filename + ':' + e.lineno + ':' + e.colno + ')']);
111
+ });
112
+ window.addEventListener('unhandledrejection', function(e) {
113
+ send('error', ['Unhandled rejection: ' + e.reason]);
114
+ });
115
+ window.parent.postMessage({ type: 'playground:ready' }, '*');
116
+ })();
117
+ <\/script>
118
+ <script type="module">${m(r)}<\/script>
119
+ </body>
120
+ </html>`;
121
+ }
122
+ var g = [
123
+ "html",
124
+ "css",
125
+ "js"
126
+ ], _ = {
127
+ html: "HTML",
128
+ css: "CSS",
129
+ js: "JS"
130
+ }, v = /* @__PURE__ */ new WeakMap(), y = class extends t {
131
+ static observedAttributes = [
132
+ "orientation",
133
+ "auto-run",
134
+ "debounce",
135
+ "active-tab",
136
+ "console-open",
137
+ "readonly",
138
+ "preview-theme",
139
+ "css-url",
140
+ "register-url"
141
+ ];
142
+ #e;
143
+ #t = /* @__PURE__ */ new Map();
144
+ #n = null;
145
+ #r;
146
+ #i;
147
+ #a = [];
148
+ #o = [];
149
+ #s = null;
150
+ /** Manually trigger a preview update. */
151
+ run() {
152
+ this.#p();
153
+ }
154
+ /** Reset all code to initial values. */
155
+ reset() {
156
+ this.#m();
157
+ }
158
+ /** Get the current code from all three editors. */
159
+ getCode() {
160
+ return {
161
+ html: this.#e.html.value,
162
+ css: this.#e.css.value,
163
+ js: this.#e.js.value
164
+ };
165
+ }
166
+ /** Programmatically set code in one or more editors. */
167
+ setCode(e) {
168
+ for (let t of g) e[t] !== void 0 && (this.#e[t].value = e[t], this.#t.get(t)?.setCode(e[t]));
169
+ this.#e.autoRun.value && this.#f();
170
+ }
171
+ attributeChangedCallback(e, t, n) {
172
+ if (t !== n && this.#e) {
173
+ switch (e) {
174
+ case "orientation":
175
+ this.#e.orientation.value = n ?? "auto";
176
+ break;
177
+ case "auto-run":
178
+ this.#e.autoRun.value = n !== null;
179
+ break;
180
+ case "debounce":
181
+ this.#e.debounce.value = n === null ? 300 : Number(n) || 300;
182
+ break;
183
+ case "active-tab":
184
+ (n === "html" || n === "css" || n === "js") && (this.#e.activeTab.value = n);
185
+ break;
186
+ case "console-open":
187
+ this.#e.consoleOpen.value = n !== null;
188
+ break;
189
+ case "readonly":
190
+ this.#e.readonly.value = n !== null;
191
+ break;
192
+ case "preview-theme":
193
+ this.#e.previewTheme.value = n ?? "";
194
+ break;
195
+ }
196
+ super.attributeChangedCallback(e, t, n);
197
+ }
198
+ }
199
+ setup() {
200
+ super.setup(), this.#e = d("", "", ""), this.#g(), this.#i = new e(this, { value: () => this.#e[this.#e.activeTab.value].value }), this.#c(), this.addEffect(() => {
201
+ let e = this.#e.activeTab.value;
202
+ for (let t = 0; t < g.length; t++) {
203
+ let n = g[t] === e;
204
+ this.#a[t].setAttribute("aria-selected", String(n)), this.#o[t].hidden = !n;
205
+ }
206
+ }), this.addEffect(() => {
207
+ let e = this.#e.consoleOpen.value;
208
+ this.#s && (this.#s.hidden = !e);
209
+ }), this.addEffect(() => {
210
+ let e = this.#e.consoleEntries.value;
211
+ this.#h(e);
212
+ }), this.addEffect(() => {
213
+ this.#e.html.value, this.#e.css.value, this.#e.js.value, this.#e.autoRun.value && this.#f();
214
+ }), window.addEventListener("message", this.#S), this.deferChildren(() => {
215
+ this.#u(), this.#d(), this.#p();
216
+ });
217
+ }
218
+ teardown() {
219
+ window.removeEventListener("message", this.#S), clearTimeout(this.#r);
220
+ for (let e of this.#t.values()) e.destroy();
221
+ this.#t.clear(), this.#i.destroy(), this.#n = null, this.#s = null, this.#a = [], this.#o = [], super.teardown();
222
+ }
223
+ #c() {
224
+ let e = document.createElement("div");
225
+ e.className = "pg-toolbar";
226
+ let t = this.#l("pg-btn pg-btn-run", "Run", "▶ Run");
227
+ t.addEventListener("click", this.#_);
228
+ let n = this.#l("pg-btn pg-btn-reset", "Reset", "↺ Reset");
229
+ n.addEventListener("click", this.#v);
230
+ let r = this.#l("pg-btn pg-btn-copy", "Copy", "Copy");
231
+ r.addEventListener("click", this.#y);
232
+ let i = document.createElement("span");
233
+ i.className = "pg-spacer";
234
+ let a = this.#l("pg-btn pg-btn-console", "Console", "Console");
235
+ a.addEventListener("click", this.#b), e.append(t, n, r, i, a);
236
+ let o = document.createElement("div");
237
+ o.className = "pg-split";
238
+ let s = document.createElement("div");
239
+ s.className = "pg-editor";
240
+ let c = document.createElement("div");
241
+ c.className = "pg-tabs", c.setAttribute("role", "tablist");
242
+ for (let e of g) {
243
+ let t = document.createElement("button");
244
+ t.className = "pg-tab", t.setAttribute("role", "tab"), t.setAttribute("aria-selected", e === this.#e.activeTab.value ? "true" : "false"), t.textContent = _[e], v.set(t, e), t.addEventListener("click", this.#x), c.appendChild(t), this.#a.push(t);
245
+ }
246
+ s.appendChild(c);
247
+ for (let e = 0; e < g.length; e++) {
248
+ let t = document.createElement("div");
249
+ t.className = "pg-code-panel", t.setAttribute("role", "tabpanel"), t.hidden = g[e] !== this.#e.activeTab.value, s.appendChild(t), this.#o.push(t);
250
+ }
251
+ let l = document.createElement("div");
252
+ l.className = "pg-console", l.hidden = !this.#e.consoleOpen.value, s.appendChild(l), this.#s = l, o.appendChild(s);
253
+ let u = document.createElement("div");
254
+ u.className = "pg-preview";
255
+ let d = document.createElement("iframe");
256
+ d.setAttribute("sandbox", "allow-scripts"), d.setAttribute("title", "Preview"), u.appendChild(d), this.#n = d, o.appendChild(u), this.append(e, o);
257
+ }
258
+ #l(e, t, n) {
259
+ let r = document.createElement("button");
260
+ return r.className = e, r.title = t, r.textContent = n, r;
261
+ }
262
+ #u() {
263
+ let e = this.querySelectorAll("script[type^=\"playground/\"]"), t = "", n = "", r = "";
264
+ for (let i of e) {
265
+ let e = (i.getAttribute("type") ?? "").replace("playground/", ""), a = (i.textContent ?? "").trim();
266
+ switch (e) {
267
+ case "html":
268
+ t = a;
269
+ break;
270
+ case "css":
271
+ n = a;
272
+ break;
273
+ case "js":
274
+ r = a;
275
+ break;
276
+ }
277
+ i.remove();
278
+ }
279
+ this.#e.html.value = t, this.#e.css.value = n, this.#e.js.value = r, this.#e.initialHtml = t, this.#e.initialCss = n, this.#e.initialJs = r;
280
+ }
281
+ #d() {
282
+ let e = this.#e.readonly.value;
283
+ for (let t = 0; t < g.length; t++) {
284
+ let n = g[t], r = this.#o[t], i = this.#e[n], a = p({
285
+ parent: r,
286
+ initialCode: i.value,
287
+ language: n,
288
+ readonly: e,
289
+ onChange: (e) => {
290
+ i.value = e, this.dispatchEvent(new CustomEvent("playground:change", {
291
+ bubbles: !0,
292
+ detail: {
293
+ tab: n,
294
+ code: e
295
+ }
296
+ }));
297
+ }
298
+ });
299
+ this.#t.set(n, a);
300
+ }
301
+ }
302
+ #f() {
303
+ clearTimeout(this.#r), this.#r = setTimeout(() => {
304
+ this.#p();
305
+ }, this.#e.debounce.value);
306
+ }
307
+ #p() {
308
+ let e = this.#n;
309
+ if (!e) return;
310
+ let t = this.getAttribute("css-url") ?? "", n = this.getAttribute("register-url") ?? "", r = this.#e.html.value, i = this.#e.css.value, a = this.#e.js.value;
311
+ this.#e.consoleEntries.value = [], e.srcdoc = h({
312
+ html: r,
313
+ css: i,
314
+ js: a,
315
+ cssUrl: t,
316
+ registerUrl: n,
317
+ themeOverrides: this.#e.previewTheme.value || void 0
318
+ }), this.dispatchEvent(new CustomEvent("playground:run", {
319
+ bubbles: !0,
320
+ detail: {
321
+ html: r,
322
+ css: i,
323
+ js: a
324
+ }
325
+ }));
326
+ }
327
+ #m() {
328
+ let { initialHtml: e, initialCss: t, initialJs: n } = this.#e;
329
+ this.#e.html.value = e, this.#e.css.value = t, this.#e.js.value = n, this.#t.get("html")?.setCode(e), this.#t.get("css")?.setCode(t), this.#t.get("js")?.setCode(n), this.dispatchEvent(new CustomEvent("playground:reset", {
330
+ bubbles: !0,
331
+ detail: {
332
+ html: e,
333
+ css: t,
334
+ js: n
335
+ }
336
+ })), this.#p();
337
+ }
338
+ #h(e) {
339
+ let t = this.#s;
340
+ if (t) {
341
+ t.textContent = "";
342
+ for (let n of e) {
343
+ if (n.level === "clear") {
344
+ t.textContent = "";
345
+ continue;
346
+ }
347
+ let e = document.createElement("div");
348
+ e.className = "pg-console-line", n.level === "warn" && e.classList.add("pg-console-warn"), n.level === "error" && e.classList.add("pg-console-error"), e.textContent = n.args.join(" "), t.appendChild(e);
349
+ }
350
+ t.scrollTop = t.scrollHeight;
351
+ }
352
+ }
353
+ #g() {
354
+ let e = this.getAttribute("orientation");
355
+ (e === "horizontal" || e === "vertical" || e === "auto") && (this.#e.orientation.value = e), this.hasAttribute("auto-run") && (this.#e.autoRun.value = !0);
356
+ let t = this.getAttribute("debounce");
357
+ t !== null && (this.#e.debounce.value = Number(t) || 300);
358
+ let n = this.getAttribute("active-tab");
359
+ (n === "html" || n === "css" || n === "js") && (this.#e.activeTab.value = n), this.hasAttribute("console-open") && (this.#e.consoleOpen.value = !0), this.hasAttribute("readonly") && (this.#e.readonly.value = !0);
360
+ let r = this.getAttribute("preview-theme");
361
+ r && (this.#e.previewTheme.value = r);
362
+ }
363
+ #_ = () => {
364
+ this.#p();
365
+ };
366
+ #v = () => {
367
+ this.#m();
368
+ };
369
+ #y = async () => {
370
+ let e = this.#e.activeTab.value, t = this.#e[e].value;
371
+ await this.#i.copy(), this.dispatchEvent(new CustomEvent("playground:copy", {
372
+ bubbles: !0,
373
+ detail: {
374
+ tab: e,
375
+ code: t
376
+ }
377
+ }));
378
+ };
379
+ #b = () => {
380
+ this.#e.consoleOpen.value = !this.#e.consoleOpen.value;
381
+ };
382
+ #x = (e) => {
383
+ let t = e.currentTarget, n = v.get(t);
384
+ n && (this.#e.activeTab.value = n);
385
+ };
386
+ #S = (e) => {
387
+ if (e.source !== this.#n?.contentWindow) return;
388
+ let t = e.data;
389
+ if (!(!t || typeof t != "object") && t.type === "playground:console") {
390
+ let e = {
391
+ level: t.level ?? "log",
392
+ args: Array.isArray(t.args) ? t.args : []
393
+ };
394
+ this.#e.consoleEntries.value = [...this.#e.consoleEntries.value, e];
395
+ }
396
+ };
397
+ };
398
+ export { d as i, h as n, p as r, y as t };
@@ -0,0 +1,44 @@
1
+ import { UIElement } from '@nonoun/native-ui';
2
+ import type { TabName } from './editors.ts';
3
+ /**
4
+ * Interactive code playground with live preview.
5
+ *
6
+ * Renders a tabbed code editor (CodeMirror), a sandboxed iframe preview,
7
+ * toolbar controls, and an optional console panel. Content is sourced from
8
+ * `<script type="playground/html|css|js">` children at setup time.
9
+ *
10
+ * @attr {string} orientation - Layout direction: "horizontal" | "vertical" | "auto" (default "auto")
11
+ * @attr {boolean} auto-run - Auto-run preview on code changes (default true)
12
+ * @attr {number} debounce - Debounce delay in ms for auto-run (default 300)
13
+ * @attr {string} active-tab - Active editor tab: "html" | "css" | "js" (default "html")
14
+ * @attr {boolean} console-open - Whether the console panel is visible
15
+ * @attr {boolean} readonly - Disable editing
16
+ * @attr {string} preview-theme - OKLCH token overrides as CSS custom property string
17
+ * @attr {string} css-url - URL to native-ui.css for the iframe (required)
18
+ * @attr {string} register-url - URL to native-ui register script for the iframe (required)
19
+ *
20
+ * @fires playground:run - When the preview updates, detail: { html, css, js }
21
+ * @fires playground:change - When user edits code, detail: { tab, code }
22
+ * @fires playground:copy - When code is copied, detail: { tab, code }
23
+ * @fires playground:reset - When code is reset, detail: { html, css, js }
24
+ */
25
+ export declare class NuiPlayground extends UIElement {
26
+ #private;
27
+ static observedAttributes: string[];
28
+ /** Manually trigger a preview update. */
29
+ run(): void;
30
+ /** Reset all code to initial values. */
31
+ reset(): void;
32
+ /** Get the current code from all three editors. */
33
+ getCode(): {
34
+ html: string;
35
+ css: string;
36
+ js: string;
37
+ };
38
+ /** Programmatically set code in one or more editors. */
39
+ setCode(code: Partial<Record<TabName, string>>): void;
40
+ attributeChangedCallback(name: string, old: string | null, val: string | null): void;
41
+ setup(): void;
42
+ teardown(): void;
43
+ }
44
+ //# sourceMappingURL=nui-playground-element.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nui-playground-element.d.ts","sourceRoot":"","sources":["../src/nui-playground-element.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAkB,MAAM,mBAAmB,CAAC;AAI9D,OAAO,KAAK,EAAkB,OAAO,EAAE,MAAM,cAAc,CAAC;AAS5D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,aAAc,SAAQ,SAAS;;IAC1C,MAAM,CAAC,kBAAkB,WAUvB;IAeF,yCAAyC;IACzC,GAAG,IAAI,IAAI;IAIX,wCAAwC;IACxC,KAAK,IAAI,IAAI;IAIb,mDAAmD;IACnD,OAAO,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE;IAQpD,wDAAwD;IACxD,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI;IAYrD,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAqCpF,KAAK,IAAI,IAAI;IAkEb,QAAQ,IAAI,IAAI;CAoWjB"}
@@ -0,0 +1,208 @@
1
+ @layer ui {
2
+ /* --- Host element --- */
3
+ :where(nui-playground) {
4
+ --n-pg-min-height: 400px;
5
+ --n-pg-radius: var(--ui-radius-lg, 0.75rem);
6
+ --n-pg-border: 1px solid color-mix(in oklch, var(--n-border-muted-neutral, #e0e0e0), transparent 50%);
7
+ --n-pg-chrome: #21252b;
8
+ --n-pg-editor-bg: #282c34;
9
+ --n-pg-preview-bg: var(--n-body-neutral, #ffffff);
10
+ --n-pg-console-bg: #1e2127;
11
+ --n-pg-console-max-height: 200px;
12
+ --n-pg-toolbar-height: 2.5rem;
13
+ --n-pg-text: #abb2bf;
14
+ --n-pg-text-muted: #6b7280;
15
+
16
+ display: grid;
17
+ grid-template-rows: var(--n-pg-toolbar-height) 1fr;
18
+ min-height: var(--n-pg-min-height);
19
+ border-radius: var(--n-pg-radius);
20
+ border: var(--n-pg-border);
21
+ overflow: hidden;
22
+ contain: layout;
23
+ container-type: inline-size;
24
+ }
25
+
26
+ /* --- Toolbar --- */
27
+ :where(nui-playground) > :where(.pg-toolbar) {
28
+ display: flex;
29
+ align-items: center;
30
+ gap: 0.25rem;
31
+ padding-inline: 0.5rem;
32
+ border-bottom: var(--n-pg-border);
33
+ background: var(--n-pg-chrome);
34
+ }
35
+
36
+ :where(nui-playground) > :where(.pg-toolbar) :where(.pg-spacer) {
37
+ flex: 1;
38
+ }
39
+
40
+ /* Toolbar buttons */
41
+ :where(nui-playground) :where(.pg-btn) {
42
+ display: inline-flex;
43
+ align-items: center;
44
+ gap: 0.25rem;
45
+ padding: 0.25rem 0.625rem;
46
+ border: none;
47
+ border-radius: 0.25rem;
48
+ background: transparent;
49
+ color: var(--n-pg-text-muted);
50
+ font-family: inherit;
51
+ font-size: 0.75rem;
52
+ font-weight: 500;
53
+ cursor: pointer;
54
+ transition: color 150ms, background 150ms;
55
+ white-space: nowrap;
56
+ }
57
+
58
+ :where(nui-playground) :where(.pg-btn):hover {
59
+ color: var(--n-pg-text);
60
+ background: var(--n-pg-editor-bg);
61
+ }
62
+
63
+ :where(nui-playground) :where(.pg-btn-run) {
64
+ color: #98c379;
65
+ }
66
+
67
+ :where(nui-playground) :where(.pg-btn-run):hover {
68
+ color: #b5e890;
69
+ background: color-mix(in oklch, #98c379, transparent 85%);
70
+ }
71
+
72
+ /* --- Split layout (editor + preview) --- */
73
+ :where(nui-playground) > :where(.pg-split) {
74
+ display: grid;
75
+ grid-template-columns: 1fr 1fr;
76
+ min-height: 0;
77
+ overflow: hidden;
78
+ }
79
+
80
+ /* Vertical orientation */
81
+ :where(nui-playground[orientation="vertical"]) > :where(.pg-split) {
82
+ grid-template-columns: 1fr;
83
+ grid-template-rows: 1fr 1fr;
84
+ }
85
+
86
+ /* Auto orientation via container query */
87
+ @container (max-width: 600px) {
88
+ :where(nui-playground[orientation="auto"]) > :where(.pg-split),
89
+ :where(nui-playground:not([orientation])) > :where(.pg-split) {
90
+ grid-template-columns: 1fr;
91
+ grid-template-rows: 1fr 1fr;
92
+ }
93
+ }
94
+
95
+ /* --- Editor region --- */
96
+ :where(nui-playground) :where(.pg-editor) {
97
+ display: grid;
98
+ grid-template-rows: auto 1fr auto;
99
+ min-height: 0;
100
+ overflow: hidden;
101
+ background: var(--n-pg-editor-bg);
102
+ }
103
+
104
+ /* Tab bar */
105
+ :where(nui-playground) :where(.pg-tabs) {
106
+ display: flex;
107
+ gap: 0;
108
+ background: var(--n-pg-chrome);
109
+ border-bottom: var(--n-pg-border);
110
+ }
111
+
112
+ :where(nui-playground) :where(.pg-tab) {
113
+ padding: 0.375rem 0.75rem;
114
+ font-family: inherit;
115
+ font-size: 0.8125rem;
116
+ font-weight: 500;
117
+ cursor: pointer;
118
+ border: none;
119
+ background: transparent;
120
+ color: var(--n-pg-text-muted);
121
+ border-bottom: 2px solid transparent;
122
+ transition: color 150ms, border-color 150ms;
123
+ }
124
+
125
+ :where(nui-playground) :where(.pg-tab):hover {
126
+ color: var(--n-pg-text);
127
+ }
128
+
129
+ :where(nui-playground) :where(.pg-tab[aria-selected="true"]) {
130
+ color: #e5e7eb;
131
+ border-bottom-color: #61afef;
132
+ }
133
+
134
+ /* Code panels */
135
+ :where(nui-playground) :where(.pg-code-panel) {
136
+ min-height: 0;
137
+ overflow: auto;
138
+ }
139
+
140
+ :where(nui-playground) :where(.pg-code-panel[hidden]) {
141
+ display: none;
142
+ }
143
+
144
+ /* CodeMirror overrides */
145
+ :where(nui-playground) :where(.cm-editor) {
146
+ height: 100%;
147
+ font-size: 0.8125rem;
148
+ }
149
+
150
+ :where(nui-playground) :where(.cm-scroller) {
151
+ overflow: auto;
152
+ }
153
+
154
+ :where(nui-playground) :where(.cm-gutters) {
155
+ border-right: none;
156
+ }
157
+
158
+ /* --- Console --- */
159
+ :where(nui-playground) :where(.pg-console) {
160
+ max-height: var(--n-pg-console-max-height);
161
+ overflow-y: auto;
162
+ border-top: var(--n-pg-border);
163
+ background: var(--n-pg-console-bg);
164
+ font-family: ui-monospace, 'SF Mono', 'Cascadia Code', monospace;
165
+ font-size: 0.75rem;
166
+ padding: 0.375rem 0.5rem;
167
+ color: var(--n-pg-text);
168
+ }
169
+
170
+ :where(nui-playground) :where(.pg-console[hidden]) {
171
+ display: none;
172
+ }
173
+
174
+ :where(nui-playground) :where(.pg-console-line) {
175
+ padding: 0.125rem 0;
176
+ white-space: pre-wrap;
177
+ word-break: break-all;
178
+ }
179
+
180
+ :where(nui-playground) :where(.pg-console-warn) {
181
+ color: #e5c07b;
182
+ }
183
+
184
+ :where(nui-playground) :where(.pg-console-error) {
185
+ color: #e06c75;
186
+ }
187
+
188
+ /* --- Preview --- */
189
+ :where(nui-playground) :where(.pg-preview) {
190
+ min-height: 0;
191
+ overflow: hidden;
192
+ background: var(--n-pg-preview-bg);
193
+ border-left: var(--n-pg-border);
194
+ }
195
+
196
+ :where(nui-playground[orientation="vertical"]) :where(.pg-preview),
197
+ :where(nui-playground:not([orientation])) :where(.pg-preview) {
198
+ border-left: none;
199
+ border-top: var(--n-pg-border);
200
+ }
201
+
202
+ :where(nui-playground) :where(.pg-preview) > :where(iframe) {
203
+ display: block;
204
+ width: 100%;
205
+ height: 100%;
206
+ border: none;
207
+ }
208
+ }
@@ -0,0 +1,2 @@
1
+ import { i as e, n as t, r as n, t as r } from "./nui-playground-element-DPT7rZeW.js";
2
+ export { r as NuiPlayground, t as buildSrcdoc, n as createEditor, e as createPlaygroundStore };
@@ -0,0 +1,24 @@
1
+ import type { Signal } from '@nonoun/native-ui';
2
+ export interface ConsoleEntry {
3
+ level: 'log' | 'warn' | 'error' | 'clear';
4
+ args: string[];
5
+ }
6
+ export interface PlaygroundStore {
7
+ html: Signal<string>;
8
+ css: Signal<string>;
9
+ js: Signal<string>;
10
+ initialHtml: string;
11
+ initialCss: string;
12
+ initialJs: string;
13
+ activeTab: Signal<'html' | 'css' | 'js'>;
14
+ consoleOpen: Signal<boolean>;
15
+ fullscreen: Signal<boolean>;
16
+ orientation: Signal<'horizontal' | 'vertical' | 'auto'>;
17
+ autoRun: Signal<boolean>;
18
+ debounce: Signal<number>;
19
+ readonly: Signal<boolean>;
20
+ consoleEntries: Signal<ConsoleEntry[]>;
21
+ previewTheme: Signal<string>;
22
+ }
23
+ export declare function createPlaygroundStore(initialHtml: string, initialCss: string, initialJs: string): PlaygroundStore;
24
+ //# sourceMappingURL=playground-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playground-store.d.ts","sourceRoot":"","sources":["../src/playground-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IAC1C,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAE9B,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAGnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAGlB,SAAS,EAAE,MAAM,CAAC,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC;IACzC,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC,YAAY,GAAG,UAAU,GAAG,MAAM,CAAC,CAAC;IACxD,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAG1B,cAAc,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;IAGvC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;CAC9B;AAED,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,eAAe,CAsBjB"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Register nui-playground
3
+ *
4
+ * Side-effect module that registers the <nui-playground> custom element.
5
+ *
6
+ * Usage:
7
+ * import '@nonoun/native-playground/register';
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=register.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,11 @@
1
+ import { t as e } from "./nui-playground-element-DPT7rZeW.js";
2
+ import { define as t } from "@nonoun/native-ui";
3
+ /**
4
+ * Register nui-playground
5
+ *
6
+ * Side-effect module that registers the <nui-playground> custom element.
7
+ *
8
+ * Usage:
9
+ * import '@nonoun/nui-playground/register';
10
+ */
11
+ t("nui-playground", e);
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@nonoun/native-playground",
3
+ "version": "0.2.0",
4
+ "description": "Embeddable live code sandbox for @nonoun/native-ui",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/native-playground.js",
8
+ "module": "./dist/native-playground.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/native-playground.js"
14
+ },
15
+ "./register": {
16
+ "types": "./dist/register.d.ts",
17
+ "default": "./dist/register.js"
18
+ },
19
+ "./css": "./dist/native-playground.css"
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "sideEffects": [
25
+ "./dist/register.js"
26
+ ],
27
+ "peerDependencies": {
28
+ "@nonoun/native-ui": ">=0.5.0"
29
+ },
30
+ "dependencies": {
31
+ "@nonoun/native-codemirror": "^0.1.0",
32
+ "@codemirror/lang-css": "^6.3.1",
33
+ "@codemirror/lang-html": "^6.4.9",
34
+ "@codemirror/lang-javascript": "^6.2.3"
35
+ },
36
+ "scripts": {
37
+ "build": "npm run build:js && npm run build:css && npm run build:types",
38
+ "build:js": "vite build",
39
+ "build:css": "cp src/css/native-playground.css dist/native-playground.css",
40
+ "build:types": "tsc -p tsconfig.build.json"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ }
45
+ }