@jogak/ui 0.1.0-alpha.7.1 → 0.1.0-alpha.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jogak/ui",
3
- "version": "0.1.0-alpha.7.1",
3
+ "version": "0.1.0-alpha.9",
4
4
  "description": "Showcase viewer UI for Jogak — Sidebar / Preview / Controls / Actions and the JogakApp shell.",
5
5
  "keywords": [
6
6
  "jogak",
@@ -24,19 +24,19 @@
24
24
  },
25
25
  "type": "module",
26
26
  "sideEffects": false,
27
- "main": "./dist/index.js",
27
+ "main": "./dist/index.cjs",
28
28
  "module": "./dist/index.mjs",
29
29
  "types": "./dist/index.d.ts",
30
30
  "exports": {
31
31
  ".": {
32
32
  "types": "./dist/index.d.ts",
33
33
  "import": "./dist/index.mjs",
34
- "require": "./dist/index.js"
34
+ "require": "./dist/index.cjs"
35
35
  },
36
36
  "./host": {
37
37
  "types": "./dist/host/index.d.ts",
38
38
  "import": "./dist/host/index.mjs",
39
- "require": "./dist/host/index.js"
39
+ "require": "./dist/host/index.cjs"
40
40
  }
41
41
  },
42
42
  "files": [
@@ -65,8 +65,8 @@
65
65
  "prism-react-renderer": "^2.4.1",
66
66
  "tailwindcss": "^4.0.0",
67
67
  "@tailwindcss/vite": "^4.0.0",
68
- "@jogak/core": "0.1.0-alpha.7.1",
69
- "@jogak/react": "0.1.0-alpha.7.1"
68
+ "@jogak/core": "0.1.0-alpha.9",
69
+ "@jogak/react": "0.1.0-alpha.9"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^20.14.0",
package/src/app/App.tsx CHANGED
@@ -21,15 +21,28 @@ export interface JogakAppProps {
21
21
  readonly metas?: readonly RegistryEntryMeta[]
22
22
  readonly codeTheme?: string
23
23
  /**
24
- * 알파.7: Preview 영역 격리 모드. default `'none'`.
24
+ * 알파.8: Preview 영역 격리 모드. default `'iframe'`.
25
25
  *
26
- * - `'none'` — Preview 콘텐츠를 chrome 같은 document렌더 (알파.6까지의 동작).
27
- * - `'shadow'` — ShadowRoot 안에 마운트. 사용자 globalCss/reset이 chrome 침범 차단.
28
- * - `'iframe'` — 별도 document(iframe)마운트. 가장 강한 격리.
26
+ * - `'iframe'` (default) 사용자 vite 정상 client(iframe)마운트. 사용자 utility 정상 컴파일.
27
+ * - `'shadow'` (deprecated) — ShadowRoot 안에 마운트. 사용자 utility 미적용.
28
+ * - `'none'` (deprecated) chrome 같은 document에 렌더. 알파.6까지의 동작.
29
29
  *
30
30
  * 자세한 트레이드오프는 `@jogak/ui` README의 "previewIsolation 사용 가이드" 참조.
31
31
  */
32
32
  readonly previewIsolation?: 'none' | 'shadow' | 'iframe'
33
+ /**
34
+ * 알파.9: 어댑터 dev URL. iframe `src` base.
35
+ * 빈 문자열 시 fallback (jogak SPA Vite scope의 preview-frame.tsx).
36
+ */
37
+ readonly userPreviewUrl?: string
38
+ /**
39
+ * 알파.9: iframe entry path (예: `/__jogak_preview__/index.html`).
40
+ */
41
+ readonly previewEntryPath?: string
42
+ /**
43
+ * @deprecated 알파.10 제거 예정. `userPreviewUrl` 사용.
44
+ */
45
+ readonly userViteUrl?: string
33
46
  }
34
47
 
35
48
  function readUrlParams(): { entryId: string; jogakName: string | null } | null {
@@ -52,8 +65,13 @@ export function JogakApp({
52
65
  entries,
53
66
  metas,
54
67
  codeTheme = 'vsDark',
55
- previewIsolation = 'shadow',
68
+ previewIsolation = 'iframe',
69
+ userPreviewUrl = '',
70
+ previewEntryPath = '/__jogak_preview__/index.html',
71
+ userViteUrl,
56
72
  }: JogakAppProps = {}): ReactElement {
73
+ // 알파.9: userViteUrl alias (deprecated). userPreviewUrl 우선.
74
+ const resolvedPreviewUrl = userPreviewUrl !== '' ? userPreviewUrl : (userViteUrl ?? '')
57
75
  // ── 4가지 모드 결정 (계약 §5.2) ─────────────────────────────────────
58
76
  // 1) entries가 주어지면: 새 ComponentRegistry에 register (eager, 기존 동작)
59
77
  // 2) metas만 주어지면: defaultRegistry 사용 + metas를 registerMeta로 등록
@@ -156,6 +174,8 @@ export function JogakApp({
156
174
  codeTheme={codeTheme}
157
175
  onResolveJogak={handleResolveJogak}
158
176
  previewIsolation={previewIsolation}
177
+ userPreviewUrl={resolvedPreviewUrl}
178
+ previewEntryPath={previewEntryPath}
159
179
  />
160
180
  ) : (
161
181
  <div className="jogak:flex jogak:items-center jogak:justify-center jogak:h-full jogak:text-[var(--jogak-color-fg-subtle)]">
package/src/app/main.tsx CHANGED
@@ -1,16 +1,17 @@
1
1
  import { StrictMode } from 'react'
2
2
  import { createRoot } from 'react-dom/client'
3
3
  import 'virtual:jogak'
4
- import { _jogakCodeTheme, _jogakPreviewIsolation } from 'virtual:jogak'
4
+ import {
5
+ _jogakCodeTheme,
6
+ _jogakPreviewIsolation,
7
+ _jogakUserPreviewUrl,
8
+ _jogakPreviewEntryPath,
9
+ } from 'virtual:jogak'
5
10
  import '../styles/jogak.css'
6
11
  import { JogakApp } from './App.js'
7
12
 
8
- // 알파.7.1: 사용자 globalCss는 isolation === 'none'일 때만 outer document에 inject.
9
- // - 'shadow'/'iframe' 모드에서는 ShadowMount/preview-frame.tsx가 자기 scope에서
10
- // 사용자 css를 자체 import하므로 outer document inject가 불필요하고, 오히려
11
- // chrome을 침범한다 (알파.7 결함).
12
- // - top-level await로 가드 — Vite는 string literal specifier의 dynamic import를
13
- // 정적 분석하여 별도 chunk + css HMR 표준 경로로 처리한다.
13
+ // 알파.9: 사용자 globalCss는 어댑터 scope(iframe entry)에서 처리되므로 jogak SPA outer
14
+ // document에는 import하지 않는다. 'none' 모드(deprecated)에서만 outer inject.
14
15
  if (_jogakPreviewIsolation === 'none') {
15
16
  await import('virtual:jogak/global-css')
16
17
  }
@@ -23,6 +24,8 @@ createRoot(rootEl).render(
23
24
  <JogakApp
24
25
  codeTheme={_jogakCodeTheme}
25
26
  previewIsolation={_jogakPreviewIsolation}
27
+ userPreviewUrl={_jogakUserPreviewUrl}
28
+ previewEntryPath={_jogakPreviewEntryPath}
26
29
  />
27
30
  </StrictMode>,
28
31
  )
@@ -1,46 +1,61 @@
1
1
  /**
2
- * 알파.7: previewIsolation='iframe' 모드 iframe document entry.
2
+ * 알파.9: standalone-adapter 또는 fallback 시 사용되는 same-origin iframe entry.
3
3
  *
4
- * - 부모 Preview 컴포넌트의 `<IframeMount>`가 `iframe.contentWindow.__jogak_setProps__`
5
- * 호출해 entry/args를 주입한다.
6
- * - iframe 부모는 동일 origin (Vite dev server) → contentWindow 직접 접근 가능.
7
- * postMessage는 cross-origin/iframe sandbox 시나리오에서만 필요.
8
- * - 사용자 globalCss(`virtual:jogak/global-css`)만 import jogak.css는 chrome 전용
9
- * 이라 iframe에서는 미필요. 사용자 reset이 iframe document에 free하게 적용됨.
4
+ * `IframeMount`는 알파.9에서 postMessage 프로토콜로 통일됐다 (cross-origin 어댑터와 동일).
5
+ * preview-frame.tsx도 같은 프로토콜을 따라야 한다 — 부모는 `jogak:setProps` 메시지를,
6
+ * iframe `jogak:ready` / `jogak:rendered` / `jogak:error`를 emit한다.
7
+ *
8
+ * jogak host vite scope에서 동작하므로 `virtual:jogak` (registry metas + entry loader)와
9
+ * `virtual:jogak/global-css` (사용자 globalCss) 가상 모듈을 그대로 사용한다.
10
10
  */
11
11
  import { reactAdapter } from '@jogak/react'
12
- import type { RegistryEntry } from '@jogak/core'
12
+ import { defaultRegistry } from '@jogak/core'
13
13
  import 'virtual:jogak'
14
14
  import 'virtual:jogak/global-css'
15
15
 
16
- interface SetPropsArgs {
17
- readonly entry: RegistryEntry
18
- readonly args: Readonly<Record<string, unknown>>
19
- }
20
-
21
- declare global {
22
- interface Window {
23
- __jogak_setProps__?: (args: SetPropsArgs) => void
24
- __jogak_unmount__?: () => void
25
- }
26
- }
27
-
28
16
  const rootEl = document.getElementById('jogak-preview-root')
29
17
  if (rootEl === null) throw new Error('#jogak-preview-root not found')
30
18
 
31
- let currentEl: HTMLDivElement | null = null
19
+ let currentContainer: HTMLDivElement | null = null
32
20
 
33
- window.__jogak_setProps__ = ({ entry, args }) => {
34
- if (currentEl === null) {
35
- currentEl = document.createElement('div')
36
- rootEl.replaceChildren(currentEl)
21
+ async function renderEntry(
22
+ entryId: string,
23
+ args: Readonly<Record<string, unknown>>,
24
+ ): Promise<void> {
25
+ const entry = await defaultRegistry.requestEntry(entryId)
26
+ if (currentContainer === null) {
27
+ currentContainer = document.createElement('div')
28
+ rootEl?.replaceChildren(currentContainer)
37
29
  }
38
- reactAdapter.render(entry, args, currentEl)
30
+ reactAdapter.render(entry, args, currentContainer)
39
31
  }
40
32
 
41
- window.__jogak_unmount__ = () => {
42
- if (currentEl !== null) {
43
- reactAdapter.unmount(currentEl)
44
- currentEl = null
33
+ function unmount(): void {
34
+ if (currentContainer !== null) {
35
+ reactAdapter.unmount(currentContainer)
36
+ currentContainer = null
45
37
  }
46
38
  }
39
+
40
+ window.addEventListener('message', (event: MessageEvent) => {
41
+ const data = event.data as { type?: unknown; entryId?: unknown; args?: unknown } | null
42
+ if (data === null || typeof data !== 'object') return
43
+ if (data.type === 'jogak:setProps' && typeof data.entryId === 'string') {
44
+ const args = (data.args ?? {}) as Readonly<Record<string, unknown>>
45
+ void renderEntry(data.entryId, args)
46
+ .then(() => {
47
+ window.parent.postMessage(
48
+ { type: 'jogak:rendered', entryId: data.entryId },
49
+ '*',
50
+ )
51
+ })
52
+ .catch((err: unknown) => {
53
+ const message = err instanceof Error ? err.message : String(err)
54
+ window.parent.postMessage({ type: 'jogak:error', message }, '*')
55
+ })
56
+ } else if (data.type === 'jogak:unmount') {
57
+ unmount()
58
+ }
59
+ })
60
+
61
+ window.parent.postMessage({ type: 'jogak:ready' }, '*')
@@ -1,81 +1,98 @@
1
- import { useEffect, useRef } from 'react'
1
+ import { useEffect, useRef, useState } from 'react'
2
2
  import type { ReactElement } from 'react'
3
3
  import type { RegistryEntry } from '@jogak/core'
4
4
 
5
5
  export interface IframeMountProps {
6
6
  readonly entry: RegistryEntry
7
7
  readonly args: Readonly<Record<string, unknown>>
8
+ /**
9
+ * 알파.9: 어댑터 dev URL (예: `http://localhost:5174`).
10
+ * 빈 문자열 시 fallback (jogak SPA Vite scope의 `/preview-frame.html`).
11
+ */
12
+ readonly userPreviewUrl: string
13
+ /**
14
+ * 알파.9: iframe entry path (예: `/__jogak_preview__/index.html`).
15
+ * 어댑터의 `previewEntryMeta.devEntryPath`.
16
+ */
17
+ readonly previewEntryPath: string
8
18
  readonly className?: string
9
19
  readonly 'data-testid'?: string
10
20
  }
11
21
 
12
- interface SetPropsArgs {
13
- readonly entry: RegistryEntry
14
- readonly args: Readonly<Record<string, unknown>>
15
- }
16
-
17
- declare global {
18
- interface Window {
19
- __jogak_setProps__?: (args: SetPropsArgs) => void
20
- __jogak_unmount__?: () => void
21
- }
22
- }
23
-
24
22
  /**
25
- * 알파.7: previewIsolation='iframe' 모드의 mount 컴포넌트.
23
+ * 알파.8: previewIsolation='iframe' 모드의 mount 컴포넌트.
26
24
  *
27
- * - `<iframe src="/preview-frame.html">`을 마운트.
28
- * - iframe load `iframe.contentWindow.__jogak_setProps__({ entry, args })`를
29
- * 호출해 entry/args를 주입한다 (postMessage 미사용 — 동일 origin이므로
30
- * contentWindow 직접 접근 가능).
31
- * - entry/args 변경 시 setProps 재호출 (load 완료 이후).
25
+ * 통신:
26
+ * - 사용자 vite spawn URL이 주어지면(`userViteUrl !== ''`) iframe src를
27
+ * `${userViteUrl}/__jogak_preview__/index.html` (cross-origin)로 설정.
28
+ * - 동일 origin fallback 시 `/preview-frame.html` (jogak SPA Vite scope).
32
29
  *
33
- * HMR:
34
- * - iframe document 자체도 Vite dev server module을 import하므로 사용자 컴포넌트
35
- * 파일 변경 fast refresh가 iframe 안에서 작동.
36
- * - previewIsolation 모드 자체 변경은 가상 모듈 invalidate → full reload.
30
+ * 양쪽 모두 postMessage로 통신:
31
+ * - 부모 → iframe: `{ type: 'jogak:setProps', entryId, args }` | `{ type: 'jogak:unmount' }`
32
+ * - iframe 부모: `{ type: 'jogak:ready' }` | `{ type: 'jogak:rendered', entryId }`
37
33
  *
38
- * sandbox 미설정:
39
- * - 사용자 컴포넌트가 fetch/clipboard/storage 자유롭게 사용해야 하므로 sandbox X.
34
+ * `entry`는 객체가 아닌 **id만 전달** — iframe 안에서 `defaultRegistry.requestEntry(id)`로
35
+ * dynamic import. 사용자 vite scope의 entry 가상 모듈이 사용자 컴포넌트를 fetch하므로
36
+ * 사용자 plugins(@tailwindcss/vite, custom alias 등)이 정상 작동.
40
37
  */
41
38
  export function IframeMount({
42
39
  entry,
43
40
  args,
41
+ userPreviewUrl,
42
+ previewEntryPath,
44
43
  className,
45
44
  'data-testid': dataTestId,
46
45
  }: IframeMountProps): ReactElement {
47
46
  const iframeRef = useRef<HTMLIFrameElement | null>(null)
48
- const readyRef = useRef(false)
47
+ const [ready, setReady] = useState(false)
48
+
49
+ const src =
50
+ userPreviewUrl !== ''
51
+ ? `${userPreviewUrl}${previewEntryPath}`
52
+ : '/preview-frame.html'
49
53
 
50
- // iframe load첫 setProps
54
+ // postMessage 리스너 — iframe contentWindow 일치성 검증 처리.
51
55
  useEffect(() => {
56
+ const handler = (event: MessageEvent): void => {
57
+ const iframe = iframeRef.current
58
+ if (iframe === null) return
59
+ if (event.source !== iframe.contentWindow) return
60
+ const data = event.data
61
+ if (data == null || typeof data !== 'object') return
62
+ if (data.type === 'jogak:ready') setReady(true)
63
+ }
64
+ window.addEventListener('message', handler)
65
+ return () => {
66
+ window.removeEventListener('message', handler)
67
+ }
68
+ }, [])
69
+
70
+ // iframe ready 또는 entry/args 변경 시 setProps.
71
+ useEffect(() => {
72
+ if (!ready) return
52
73
  const iframe = iframeRef.current
53
74
  if (iframe === null) return
54
- const handleLoad = (): void => {
55
- readyRef.current = true
56
- iframe.contentWindow?.__jogak_setProps__?.({ entry, args })
57
- }
58
- iframe.addEventListener('load', handleLoad)
75
+ iframe.contentWindow?.postMessage(
76
+ { type: 'jogak:setProps', entryId: entry.id, args },
77
+ '*',
78
+ )
79
+ }, [ready, entry, args])
80
+
81
+ // unmount 시 unmount 메시지 (race 회피 microtask defer).
82
+ useEffect(() => {
83
+ const iframe = iframeRef.current
59
84
  return () => {
60
- iframe.removeEventListener('load', handleLoad)
61
- // 알파.7.1: unmount race 회피 — iframe contentWindow 정리도 microtask defer.
85
+ if (iframe === null) return
62
86
  queueMicrotask(() => {
63
- iframe.contentWindow?.__jogak_unmount__?.()
87
+ iframe.contentWindow?.postMessage({ type: 'jogak:unmount' }, '*')
64
88
  })
65
89
  }
66
- // eslint-disable-next-line react-hooks/exhaustive-deps
67
90
  }, [])
68
91
 
69
- // entry/args 변경 시 setProps 재호출 (load 후에만)
70
- useEffect(() => {
71
- if (!readyRef.current) return
72
- iframeRef.current?.contentWindow?.__jogak_setProps__?.({ entry, args })
73
- }, [entry, args])
74
-
75
92
  return (
76
93
  <iframe
77
94
  ref={iframeRef}
78
- src="/preview-frame.html"
95
+ src={src}
79
96
  title="Preview"
80
97
  className={className}
81
98
  data-testid={dataTestId}
@@ -24,13 +24,22 @@ export interface PreviewProps {
24
24
  */
25
25
  readonly onResolveJogak?: (entryId: string, jogakName: string) => void
26
26
  /**
27
- * 알파.7: Preview 영역 격리 모드. default `'none'`.
27
+ * 알파.8: Preview 영역 격리 모드. default `'iframe'`.
28
28
  *
29
- * - `'none'` — 기존 동작 (chrome과 같은 document, 알파.6 chrome 보호 rule 적용).
30
- * - `'shadow'` — ShadowRoot에 마운트. 사용자 globalCss reset이 chrome 침범 차단.
31
- * - `'iframe'` — `/preview-frame.html` iframe마운트. 강한 격리.
29
+ * - `'iframe'` (default) 사용자 vite scope에 마운트. 사용자 utility 정상 컴파일.
30
+ * - `'shadow'` (deprecated) — ShadowRoot에 마운트. 사용자 utility 미적용.
31
+ * - `'none'` (deprecated) chrome과 같은 document렌더.
32
32
  */
33
33
  readonly previewIsolation?: 'none' | 'shadow' | 'iframe'
34
+ /**
35
+ * 알파.9: 어댑터 dev URL. iframe `src` base.
36
+ * 빈 문자열 시 fallback (jogak SPA Vite scope의 `/preview-frame.html`).
37
+ */
38
+ readonly userPreviewUrl?: string
39
+ /**
40
+ * 알파.9: iframe entry path.
41
+ */
42
+ readonly previewEntryPath?: string
34
43
  }
35
44
 
36
45
  type ViewportKey = 'mobile' | 'tablet' | 'desktop'
@@ -120,7 +129,9 @@ export function Preview({
120
129
  onReset,
121
130
  codeTheme,
122
131
  onResolveJogak,
123
- previewIsolation = 'shadow',
132
+ previewIsolation = 'iframe',
133
+ userPreviewUrl = '',
134
+ previewEntryPath = '/__jogak_preview__/index.html',
124
135
  }: PreviewProps): ReactElement {
125
136
  const state = useEntry(entryId)
126
137
  const [viewport, setViewport] = useState<ViewportKey>('desktop')
@@ -187,6 +198,8 @@ export function Preview({
187
198
  onBottomTabChange={setBottomTab}
188
199
  prismTheme={prismTheme}
189
200
  previewIsolation={previewIsolation}
201
+ userPreviewUrl={userPreviewUrl}
202
+ previewEntryPath={previewEntryPath}
190
203
  />
191
204
  )
192
205
  }
@@ -275,6 +288,8 @@ interface ReadyFrameProps {
275
288
  readonly onBottomTabChange: (tab: 'controls' | 'actions') => void
276
289
  readonly prismTheme: PrismTheme
277
290
  readonly previewIsolation: 'none' | 'shadow' | 'iframe'
291
+ readonly userPreviewUrl: string
292
+ readonly previewEntryPath: string
278
293
  }
279
294
 
280
295
  function ReadyFrame({
@@ -292,6 +307,8 @@ function ReadyFrame({
292
307
  onBottomTabChange,
293
308
  prismTheme,
294
309
  previewIsolation,
310
+ userPreviewUrl,
311
+ previewEntryPath,
295
312
  }: ReadyFrameProps): ReactElement {
296
313
  // jogakName이 비어있으면 (deep link `?entry=...&jogak` 누락) 첫 jogak로 보정.
297
314
  const resolvedJogakName = jogakName ?? entry.jogaks[0]?.name ?? null
@@ -364,6 +381,8 @@ function ReadyFrame({
364
381
  source={entry.source}
365
382
  theme={prismTheme}
366
383
  previewIsolation={previewIsolation}
384
+ userPreviewUrl={userPreviewUrl}
385
+ previewEntryPath={previewEntryPath}
367
386
  />
368
387
  </div>
369
388
  </div>
@@ -514,18 +533,18 @@ interface JogakRendererProps {
514
533
  readonly source: string | undefined
515
534
  readonly theme: PrismTheme
516
535
  readonly previewIsolation: 'none' | 'shadow' | 'iframe'
536
+ readonly userPreviewUrl: string
537
+ readonly previewEntryPath: string
517
538
  }
518
539
 
519
540
  /**
520
- * 알파.7: previewIsolation 모드별로 사용자 콘텐츠 마운트 방식을 분기한다.
521
- *
522
- * - `'none'` — 같은 document에 직접 마운트 (알파.6까지의 동작 그대로).
523
- * - `'shadow'` — `<ShadowMount>` 안에 마운트해 ShadowRoot 격리.
524
- * - `'iframe'` — `<IframeMount>`로 별도 document에 마운트.
541
+ * 알파.9: previewIsolation 모드별로 사용자 콘텐츠 마운트 방식을 분기한다.
525
542
  *
526
- * Show source 토글, 코드 패널 chrome 부분은 모드 무관하게 외부에 둔다.
543
+ * - `'iframe'` (default) 어댑터 dev URL의 `<IframeMount>`로 별도 document.
544
+ * - `'shadow'` (deprecated) — `<ShadowMount>` 안에 마운트.
545
+ * - `'none'` (deprecated) — 같은 document에 직접 마운트.
527
546
  */
528
- function JogakRenderer({ entry, args, source, theme, previewIsolation }: JogakRendererProps): ReactElement {
547
+ function JogakRenderer({ entry, args, source, theme, previewIsolation, userPreviewUrl, previewEntryPath }: JogakRendererProps): ReactElement {
529
548
  const [showCode, setShowCode] = useState(false)
530
549
 
531
550
  const previewBody = (
@@ -534,6 +553,8 @@ function JogakRenderer({ entry, args, source, theme, previewIsolation }: JogakRe
534
553
  entry={entry}
535
554
  args={args}
536
555
  previewIsolation={previewIsolation}
556
+ userPreviewUrl={userPreviewUrl}
557
+ previewEntryPath={previewEntryPath}
537
558
  />
538
559
  <button
539
560
  type="button"
@@ -575,13 +596,15 @@ interface PreviewMountProps {
575
596
  readonly entry: RegistryEntry
576
597
  readonly args: Readonly<Record<string, unknown>>
577
598
  readonly previewIsolation: 'none' | 'shadow' | 'iframe'
599
+ readonly userPreviewUrl: string
600
+ readonly previewEntryPath: string
578
601
  }
579
602
 
580
603
  const PREVIEW_HOST_CLASS =
581
604
  'jogak:border jogak:border-dashed jogak:border-[var(--jogak-color-border)] ' +
582
605
  'jogak:rounded-[var(--jogak-radius-xl)] jogak:p-4 jogak:pb-9'
583
606
 
584
- function PreviewMount({ entry, args, previewIsolation }: PreviewMountProps): ReactElement {
607
+ function PreviewMount({ entry, args, previewIsolation, userPreviewUrl, previewEntryPath }: PreviewMountProps): ReactElement {
585
608
  if (previewIsolation === 'shadow') {
586
609
  return (
587
610
  <ShadowMount
@@ -598,13 +621,15 @@ function PreviewMount({ entry, args, previewIsolation }: PreviewMountProps): Rea
598
621
  <IframeMount
599
622
  entry={entry}
600
623
  args={args}
624
+ userPreviewUrl={userPreviewUrl}
625
+ previewEntryPath={previewEntryPath}
601
626
  data-testid="preview-content"
602
627
  className={`${PREVIEW_HOST_CLASS} jogak:block jogak:w-full jogak:bg-transparent jogak:min-h-[256px]`}
603
628
  />
604
629
  )
605
630
  }
606
631
 
607
- // 'none' — 기존 동작 그대로
632
+ // 'none' — deprecated 경로 (알파.7.1 동등 동작 보존, back-compat)
608
633
  return <NoneAdapterContent entry={entry} args={args} />
609
634
  }
610
635
 
package/src/vite-env.d.ts CHANGED
@@ -4,10 +4,25 @@ declare module 'virtual:jogak' {
4
4
  /** 플러그인 설정에서 지정한 prism-react-renderer 테마 이름 */
5
5
  export const _jogakCodeTheme: string
6
6
  /**
7
- * 알파.7: Preview 영역 격리 모드 ('none' | 'shadow' | 'iframe').
8
- * `JogakPluginOptions.previewIsolation` (default 'none')의 literal emit.
7
+ * 알파.8: Preview 영역 격리 모드 ('none' | 'shadow' | 'iframe').
8
+ * `JogakPluginOptions.previewIsolation` (default 'iframe')의 literal emit.
9
9
  */
10
10
  export const _jogakPreviewIsolation: 'none' | 'shadow' | 'iframe'
11
+ /**
12
+ * 알파.9: 어댑터 dev URL. iframe `src` base로 사용 (예: `http://localhost:5174`).
13
+ * 빈 문자열 시 fallback (jogak SPA Vite scope의 preview-frame.tsx).
14
+ */
15
+ export const _jogakUserPreviewUrl: string
16
+ /**
17
+ * 알파.9: iframe entry path (`BuilderAdapter.previewEntryMeta.devEntryPath`).
18
+ * 어댑터별 routing (vite: `/__jogak_preview__/index.html`).
19
+ */
20
+ export const _jogakPreviewEntryPath: string
21
+ /**
22
+ * @deprecated 알파.10 제거 예정. `_jogakUserPreviewUrl` 사용.
23
+ * 알파.8 호환 alias.
24
+ */
25
+ export const _jogakUserViteUrl: string
11
26
  }
12
27
 
13
28
  declare module 'virtual:jogak/global-css' {
@@ -1 +0,0 @@
1
- "use strict";var S=Object.create;var m=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var B=Object.getPrototypeOf,H=Object.prototype.hasOwnProperty;var N=(t,e,o,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of A(e))!H.call(t,n)&&n!==o&&m(t,n,{get:()=>e[n],enumerable:!(r=k(e,n))||r.enumerable});return t};var c=(t,e,o)=>(o=t!=null?S(B(t)):{},N(e||!t||!t.__esModule?m(o,"default",{value:t,enumerable:!0}):o,t));Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const x=require("node:url"),s=require("node:path");var f=typeof document<"u"?document.currentScript:null;const F=x.fileURLToPath(typeof document>"u"?require("url").pathToFileURL(__filename).href:f&&f.tagName.toUpperCase()==="SCRIPT"&&f.src||new URL("host/index.js",document.baseURI).href),i=s.resolve(s.dirname(F),"..",".."),E=s.resolve(i,"index.html"),Y=s.resolve(i,"src/app/main.tsx");async function q(t){const e=await import("vite"),o=await import("@vitejs/plugin-react"),r=await import("@jogak/core/vite"),n=await import("@tailwindcss/vite"),{createServer:h,build:v}=e,b=o.default,w=n.default,{jogak:_}=r,C=t.codeTheme??"vsDark",u={patterns:t.patterns,codeTheme:C,cwd:t.userRoot};t.tsConfigFilePath!==void 0&&(u.tsConfigFilePath=t.tsConfigFilePath),t.globalCss!==void 0&&(u.globalCss=t.globalCss),t.previewIsolation!==void 0&&(u.previewIsolation=t.previewIsolation);const R=t.extraPlugins??[],d={root:i,configFile:!1,plugins:[b(),w(),_(u),...R],optimizeDeps:{include:["react","react-dom/client","@jogak/core","@jogak/react"]}};if(t.mode==="dev"){const D={port:t.port??5173,host:t.host??"localhost",open:t.open??!1,fs:{allow:[i,t.userRoot]}},L={...d,server:D},a=await h(L);await a.listen();const p=a.config.server.port??t.port??5173,l=t.host??"localhost",M=`http://${typeof l=="boolean"?l?"0.0.0.0":"localhost":l}:${p.toString()}/`;let g=!1;return{url:M,port:p,close:async()=>{g||(g=!0,await a.close())},printUrls:()=>{a.printUrls()}}}const I={...d,base:t.base??"./",build:{outDir:t.outDir,emptyOutDir:!0,sourcemap:t.sourcemap??!1,minify:t.minify??"esbuild",rollupOptions:{input:{main:s.resolve(i,"index.html"),preview:s.resolve(i,"preview-frame.html")}}}},P=Date.now(),T=await v(I),U=Date.now()-P,{assetCount:O,totalBytes:j}=z(T);return{outDir:t.outDir,elapsedMs:U,assetCount:O,totalBytes:j}}function z(t){const e=$(t);if(e===void 0)return{assetCount:0,totalBytes:0};let o=0,r=0;for(const n of e)o+=1,r+=G(n);return{assetCount:o,totalBytes:r}}function y(t){return typeof t=="object"&&t!==null&&Array.isArray(t.output)}function $(t){if(Array.isArray(t)){const e=[];for(const o of t)y(o)&&e.push(...o.output);return e}if(y(t))return t.output}function G(t){if(typeof t!="object"||t===null)return 0;const e=t;if(e.type==="chunk"&&typeof e.code=="string")return Buffer.byteLength(e.code,"utf8");if(e.type==="asset"){const o=e.source;if(typeof o=="string")return Buffer.byteLength(o,"utf8");if(o instanceof Uint8Array)return o.byteLength}return 0}exports.UI_HTML_ENTRY=E;exports.UI_MAIN_ENTRY=Y;exports.runHost=q;
package/dist/index.js DELETED
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("react/jsx-runtime"),k=require("react"),y=require("@jogak/core"),m=require("@jogak/react"),_=require("prism-react-renderer"),G=require("react-dom");function J(a){var e,t,r="";if(typeof a=="string"||typeof a=="number")r+=a;else if(typeof a=="object")if(Array.isArray(a)){var j=a.length;for(e=0;e<j;e++)a[e]&&(t=J(a[e]))&&(r&&(r+=" "),r+=t)}else for(t in a)a[t]&&(r&&(r+=" "),r+=t);return r}function h(){for(var a,e,t=0,r="",j=arguments.length;t<j;t++)(a=arguments[t])&&(e=J(a))&&(r&&(r+=" "),r+=e);return r}function q({selectedEntryId:a,selectedJogakName:e,onSelect:t}){const[r,j]=k.useState(""),{metaTree:n,searchMeta:g}=m.useRegistryMeta(),l=r.trim().length>0?g(r):null;return o.jsxs("aside",{"data-testid":"sidebar",className:"jogak:flex jogak:flex-col jogak:h-full jogak:overflow-auto jogak:border-r jogak:border-[var(--jogak-color-border)]",children:[o.jsx("div",{className:"jogak:p-3 jogak:border-b jogak:border-[var(--jogak-color-border)]",children:o.jsx("input",{type:"search",placeholder:"Search components...",value:r,onChange:s=>{j(s.target.value)},className:"jogak:w-full jogak:px-2 jogak:py-1.5 jogak:border jogak:border-[var(--jogak-color-border-strong)] jogak:rounded-[var(--jogak-radius-md)]","aria-label":"Search components"})}),o.jsx("nav",{className:"jogak:flex-1 jogak:overflow-auto jogak:py-2",children:l!==null?o.jsx(I,{metas:l,selectedEntryId:a,selectedJogakName:e,onSelect:t}):o.jsx(V,{node:n,selectedEntryId:a,selectedJogakName:e,onSelect:t})})]})}function I({metas:a,selectedEntryId:e,selectedJogakName:t,onSelect:r}){return a.length===0?o.jsx("p",{className:"jogak:px-3 jogak:text-[var(--jogak-color-fg-subtle)] jogak:text-[13px]",children:"No results"}):o.jsx("ul",{className:"jogak:list-none jogak:m-0 jogak:p-0",children:a.map(j=>o.jsx("li",{children:o.jsx(B,{meta:j,selectedEntryId:e,selectedJogakName:t,onSelect:r,indent:0})},j.id))})}function V({node:a,selectedEntryId:e,selectedJogakName:t,onSelect:r,depth:j=0}){return o.jsx("ul",{className:"jogak:list-none jogak:m-0 jogak:pr-0 jogak:py-0 jogak:pl-[var(--jogak-tree-pl)]",style:{"--jogak-tree-pl":`${j*12}px`},children:Object.entries(a).map(([n,g])=>o.jsx("li",{children:"id"in g?o.jsx(B,{meta:g,selectedEntryId:e,selectedJogakName:t,onSelect:r,indent:0}):o.jsx(Q,{label:n,node:g,selectedEntryId:e,selectedJogakName:t,onSelect:r,depth:j+1})},n))})}function Q({label:a,node:e,selectedEntryId:t,selectedJogakName:r,onSelect:j,depth:n}){const[g,l]=k.useState(!0);return o.jsxs("div",{children:[o.jsxs("button",{type:"button",onClick:()=>{l(s=>!s)},className:"jogak:flex jogak:items-center jogak:gap-1 jogak:w-full jogak:px-3 jogak:py-1 jogak:bg-transparent jogak:border-none jogak:cursor-pointer jogak:text-[12px] jogak:font-semibold jogak:text-[var(--jogak-color-fg-muted)] jogak:uppercase jogak:tracking-wider","aria-expanded":g,children:[o.jsx("span",{children:g?"▾":"▸"}),a]}),g&&o.jsx(V,{node:e,selectedEntryId:t,selectedJogakName:r,onSelect:j,depth:n})]})}function B({meta:a,selectedEntryId:e,selectedJogakName:t,onSelect:r,indent:j}){const n=a.id===e,[g,l]=k.useState(n);k.useEffect(()=>{n&&l(!0)},[n]);const s=a.title.split("/").pop()??a.title,c=16+j*12;return o.jsxs("div",{children:[o.jsxs("button",{type:"button",onClick:()=>{if(n)l(i=>!i);else{l(!0);const i=a.jogakNames[0];i!==void 0&&r(a.id,i)}},className:h("jogak:flex jogak:items-center jogak:gap-1.5 jogak:w-full jogak:pr-3 jogak:py-[5px]","jogak:pl-[var(--jogak-entry-pl)]","jogak:border-none jogak:cursor-pointer jogak:text-left jogak:text-[13px]",n?"jogak:bg-[var(--jogak-color-accent-bg)] jogak:text-[var(--jogak-color-accent)] jogak:font-medium":"jogak:bg-transparent jogak:text-[var(--jogak-color-fg)] jogak:font-normal"),style:{"--jogak-entry-pl":`${c}px`},"aria-expanded":g,children:[o.jsx("span",{className:"jogak:text-[10px] jogak:shrink-0 jogak:leading-none",children:g?"▾":"▸"}),s]}),g&&o.jsx("ul",{className:"jogak:list-none jogak:m-0 jogak:p-0",children:a.jogakNames.map(i=>{const x=n&&i===t;return o.jsx("li",{children:o.jsx("button",{type:"button",onClick:()=>{r(a.id,i)},className:h("jogak:block jogak:w-full jogak:text-left jogak:pr-3 jogak:py-1","jogak:pl-[var(--jogak-jogak-pl)]","jogak:border-none jogak:cursor-pointer jogak:text-[12px]",x?"jogak:bg-[var(--jogak-color-accent-bg-soft)] jogak:text-[var(--jogak-color-accent-fg)] jogak:font-medium":"jogak:bg-transparent jogak:text-[var(--jogak-color-fg-muted)] jogak:font-normal"),style:{"--jogak-jogak-pl":`${c+18}px`},"aria-current":x?"true":void 0,children:i})},i)})})]})}function X(a,e){const t=e==null?void 0:e.control,r=(e==null?void 0:e.action)!==void 0&&e.action!==!1,j=(e==null?void 0:e.type)==="function"||typeof a=="function";return r||j?"action":t==="boolean"||typeof a=="boolean"?"boolean":t==="number"||t==="range"||typeof a=="number"?"number":t==="select"||t==="radio"||(e==null?void 0:e.options)!==void 0&&e.options.length>0?"select":t==="text"||t==="color"||typeof a=="string"?"text":"json"}const S="jogak:px-2 jogak:py-1 jogak:border jogak:border-[var(--jogak-color-border-strong)] jogak:rounded-[var(--jogak-radius-md)] jogak:text-[13px] jogak:w-full jogak:max-w-[280px]",C="jogak:px-5 jogak:py-1.5 jogak:text-left jogak:text-[var(--jogak-color-fg-muted)] jogak:font-medium jogak:text-[12px] jogak:border-b jogak:border-[var(--jogak-color-border)]",R="jogak:px-5 jogak:py-2 jogak:align-middle jogak:border-b jogak:border-[var(--jogak-color-border-muted)]";function Y({argKey:a,value:e,argType:t,onArgChange:r}){switch(X(e,t)){case"boolean":return o.jsx("input",{type:"checkbox",checked:e===!0,onChange:n=>{r(a,n.target.checked)},className:"jogak:cursor-pointer jogak:w-4 jogak:h-4 jogak:accent-[var(--jogak-color-accent)]"});case"number":return o.jsx("input",{type:"number",value:typeof e=="number"?e:"",onChange:n=>{r(a,n.target.valueAsNumber)},className:S});case"select":{const n=(t==null?void 0:t.options)??[];return o.jsx("select",{value:String(e??""),onChange:g=>{r(a,g.target.value)},className:S,children:n.map(g=>o.jsx("option",{value:String(g),children:String(g)},String(g)))})}case"text":return o.jsx("input",{type:"text",value:typeof e=="string"?e:String(e??""),onChange:n=>{r(a,n.target.value)},className:S});case"action":return o.jsx("span",{className:"jogak:inline-block jogak:px-2 jogak:py-0.5 jogak:text-[11px] jogak:font-semibold jogak:text-[var(--jogak-color-violet)] jogak:bg-[var(--jogak-color-violet-bg)] jogak:border jogak:border-[var(--jogak-color-violet-border)] jogak:rounded-[var(--jogak-radius-md)] jogak:font-[family-name:var(--jogak-font-mono)] jogak:leading-none",children:"(action)"});case"json":return o.jsx("code",{className:"jogak:text-[12px] jogak:text-[var(--jogak-color-fg-muted)] jogak:font-[family-name:var(--jogak-font-mono)]",children:JSON.stringify(e)})}}function W({args:a,argTypes:e,onArgChange:t}){const j=Array.from(new Set([...Object.keys(a),...Object.keys(e)])).map(n=>[n,a[n]]);return o.jsxs("div",{className:"jogak:border-t-2 jogak:border-[var(--jogak-color-border)]",children:[o.jsx("div",{className:"jogak:px-5 jogak:py-1.5 jogak:text-[11px] jogak:font-bold jogak:text-[var(--jogak-color-fg-subtle)] jogak:uppercase jogak:tracking-[0.08em] jogak:border-b jogak:border-[var(--jogak-color-border)] jogak:bg-[var(--jogak-color-bg-subtle)]",children:"Controls"}),j.length===0?o.jsx("div",{className:"jogak:px-5 jogak:py-3 jogak:text-[var(--jogak-color-fg-subtle)] jogak:text-[13px]",children:"No args defined"}):o.jsxs("table",{className:"jogak:w-full jogak:border-collapse jogak:text-[13px]",children:[o.jsx("thead",{children:o.jsxs("tr",{children:[o.jsx("th",{className:C,children:"Name"}),o.jsx("th",{className:C,children:"Control"}),o.jsx("th",{className:C,children:"Description"})]})}),o.jsx("tbody",{children:j.map(([n,g])=>{const l=e[n];return o.jsxs("tr",{children:[o.jsx("td",{className:h(R,"jogak:font-[family-name:var(--jogak-font-mono)] jogak:text-[12px] jogak:text-[var(--jogak-color-fg)] jogak:whitespace-nowrap"),children:n}),o.jsx("td",{className:R,children:o.jsx(Y,{argKey:n,value:g,argType:l,onArgChange:t})}),o.jsx("td",{className:h(R,"jogak:text-[var(--jogak-color-fg-subtle)]"),children:(l==null?void 0:l.description)??""})]},n)})})]})]})}function Z(a){if(a.length===0)return"()";try{return a.map(e=>{var t;if(e===null)return"null";if(e===void 0)return"undefined";if(typeof e=="function")return"[Function]";if(typeof e=="object"){const r=((t=e.constructor)==null?void 0:t.name)??"Object";return r!=="Object"&&r!=="Array"?`[${r}]`:JSON.stringify(e)}return JSON.stringify(e)}).join(", ")}catch{return"[unserializable]"}}function K(a){const e=new Date(a),t=e.getHours().toString().padStart(2,"0"),r=e.getMinutes().toString().padStart(2,"0"),j=e.getSeconds().toString().padStart(2,"0"),n=e.getMilliseconds().toString().padStart(3,"0");return`${t}:${r}:${j}.${n}`}function z(){const[a,e]=k.useState(()=>y.defaultActionChannel.getLogs());k.useEffect(()=>y.defaultActionChannel.subscribe(e),[]);const t=a.length===0;return o.jsxs("div",{className:"jogak:h-full jogak:flex jogak:flex-col",children:[o.jsxs("div",{className:"jogak:px-5 jogak:py-1.5 jogak:text-[11px] jogak:font-bold jogak:text-[var(--jogak-color-fg-subtle)] jogak:uppercase jogak:tracking-[0.08em] jogak:border-b jogak:border-[var(--jogak-color-border)] jogak:bg-[var(--jogak-color-bg-subtle)] jogak:flex jogak:items-center jogak:justify-between jogak:shrink-0",children:[o.jsxs("span",{children:["Actions ",a.length>0&&`(${a.length.toString()})`]}),o.jsx("button",{type:"button",onClick:()=>{y.defaultActionChannel.clear()},disabled:t,className:h("jogak:text-[10px] jogak:font-semibold jogak:px-2 jogak:py-0.5 jogak:border jogak:border-[var(--jogak-color-border-strong)] jogak:rounded-[var(--jogak-radius-sm)] jogak:bg-[var(--jogak-color-bg)] jogak:normal-case jogak:tracking-normal",t?"jogak:text-[var(--jogak-color-fg-subtle)] jogak:cursor-default":"jogak:text-[var(--jogak-color-fg)] jogak:cursor-pointer"),children:"Clear"})]}),o.jsx("div",{className:"jogak:flex-1 jogak:overflow-auto",children:t?o.jsx("div",{className:"jogak:px-5 jogak:py-3 jogak:text-[var(--jogak-color-fg-subtle)] jogak:text-[13px] jogak:leading-none",children:"함수 prop이 호출되면 여기에 기록됩니다"}):o.jsx("ul",{className:"jogak:list-none jogak:m-0 jogak:p-0 jogak:font-[family-name:var(--jogak-font-mono)] jogak:text-[12px]",children:a.map(r=>o.jsxs("li",{className:"jogak:flex jogak:items-baseline jogak:gap-[10px] jogak:px-5 jogak:py-1.5 jogak:border-b jogak:border-[var(--jogak-color-border-muted)]",children:[o.jsx("span",{className:"jogak:text-[var(--jogak-color-fg-subtle)] jogak:text-[11px] jogak:min-w-[92px]",children:K(r.timestamp)}),o.jsx("span",{className:"jogak:text-[var(--jogak-color-violet)] jogak:font-semibold",children:r.name}),o.jsxs("span",{className:"jogak:text-[var(--jogak-color-fg)] jogak:break-all jogak:flex-1",children:["(",Z(r.args),")"]})]},r.id))})})]})}function T({children:a,className:e,style:t,"data-testid":r}){const j=k.useRef(null),[n,g]=k.useState(null);return k.useEffect(()=>{const l=j.current;if(l===null)return;const s=l.shadowRoot??l.attachShadow({mode:"open"});g(s)},[]),o.jsx("div",{ref:j,className:e,"data-testid":r,style:t,children:n!==null?G.createPortal(a,n):null})}function oo({entry:a,args:e,className:t,"data-testid":r}){const j=k.useRef(null),n=k.useRef(!1);return k.useEffect(()=>{const g=j.current;if(g===null)return;const l=()=>{var s,c;n.current=!0,(c=(s=g.contentWindow)==null?void 0:s.__jogak_setProps__)==null||c.call(s,{entry:a,args:e})};return g.addEventListener("load",l),()=>{g.removeEventListener("load",l),queueMicrotask(()=>{var s,c;(c=(s=g.contentWindow)==null?void 0:s.__jogak_unmount__)==null||c.call(s)})}},[]),k.useEffect(()=>{var g,l,s;n.current&&((s=(l=(g=j.current)==null?void 0:g.contentWindow)==null?void 0:l.__jogak_setProps__)==null||s.call(l,{entry:a,args:e}))},[a,e]),o.jsx("iframe",{ref:j,src:"/preview-frame.html",title:"Preview",className:t,"data-testid":r})}const D={mobile:375,tablet:768,desktop:"none"},ao={mobile:"Mobile",tablet:"Tablet",desktop:"Desktop"},E={white:{"--jogak-canvas-bg":"#ffffff","--jogak-canvas-bg-image":"none","--jogak-canvas-bg-size":"auto","--jogak-canvas-bg-position":"0 0"},dark:{"--jogak-canvas-bg":"#1f2937","--jogak-canvas-bg-image":"none","--jogak-canvas-bg-size":"auto","--jogak-canvas-bg-position":"0 0"},transparent:{"--jogak-canvas-bg":"#ffffff","--jogak-canvas-bg-image":"linear-gradient(45deg, #e2e8f0 25%, transparent 25%), linear-gradient(-45deg, #e2e8f0 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #e2e8f0 75%), linear-gradient(-45deg, transparent 75%, #e2e8f0 75%)","--jogak-canvas-bg-size":"16px 16px","--jogak-canvas-bg-position":"0 0, 0 8px, 8px -8px, -8px 0px"}},$="jogak:bg-[var(--jogak-canvas-bg)] jogak:bg-[image:var(--jogak-canvas-bg-image)] jogak:bg-[length:var(--jogak-canvas-bg-size)] jogak:bg-[position:var(--jogak-canvas-bg-position)]";function eo(a){return _.themes[a]??_.themes.vsDark}function F({entryId:a,jogakName:e,overrideArgs:t,onArgChange:r,onReset:j,codeTheme:n,onResolveJogak:g,previewIsolation:l="shadow"}){const s=m.useEntry(a),[c,i]=k.useState("desktop"),[x,v]=k.useState("white"),[w,f]=k.useState("controls"),b=eo(n);return s.status==="unknown"?o.jsxs("div",{"data-testid":"preview-not-found",className:"jogak:p-6 jogak:text-[var(--jogak-color-error)]",children:["Entry not found: ",a]}):s.status==="error"?o.jsxs("div",{"data-testid":"preview-error",className:"jogak:p-6 jogak:text-[var(--jogak-color-error-fg)] jogak:bg-[var(--jogak-color-bg-error)] jogak:h-full jogak:flex jogak:flex-col jogak:gap-3 jogak:items-start",children:[o.jsxs("div",{className:"jogak:font-semibold",children:["Failed to load entry: ",a]}),o.jsx("pre",{className:"jogak:m-0 jogak:p-3 jogak:bg-[var(--jogak-color-bg)] jogak:border jogak:border-[var(--jogak-color-error-border)] jogak:rounded-[var(--jogak-radius-lg)] jogak:text-[12px] jogak:whitespace-pre-wrap jogak:max-w-full",children:s.error.message})]}):s.status==="loading"?o.jsx(to,{meta:s.meta,jogakName:e,viewport:c,bgMode:x,onViewportChange:i,onBgModeChange:v}):o.jsx(ro,{entry:s.entry,jogakName:e,overrideArgs:t,onArgChange:r,onReset:j,onResolveJogak:g,viewport:c,bgMode:x,bottomTab:w,onViewportChange:i,onBgModeChange:v,onBottomTabChange:f,prismTheme:b,previewIsolation:l})}function to({meta:a,jogakName:e,viewport:t,bgMode:r,onViewportChange:j,onBgModeChange:n}){const g=e??a.jogakNames[0]??"...",l=D[t];return o.jsxs("div",{"data-testid":"preview-loading",className:"jogak:flex jogak:flex-col jogak:h-full",children:[o.jsx(H,{title:a.title,jogakName:g,viewport:t,bgMode:r,onViewportChange:j,onBgModeChange:n,showReset:!1,onReset:()=>{}}),o.jsx("div",{className:`jogak:flex-1 jogak:overflow-auto jogak:min-h-[320px] ${$}`,style:E[r],children:o.jsx("div",{className:"jogak:mx-auto jogak:p-6 jogak:max-w-[var(--jogak-canvas-mw)]",style:{"--jogak-canvas-mw":l==="none"?"100%":`${l}px`},children:o.jsxs("div",{className:"jogak-skeleton-shimmer jogak:border jogak:border-dashed jogak:border-[var(--jogak-color-border)] jogak:rounded-[var(--jogak-radius-xl)] jogak:p-4 jogak:flex jogak:items-center jogak:justify-center jogak:text-[var(--jogak-color-fg-subtle)] jogak:text-[13px] jogak:min-h-[256px]",children:["Loading ",a.title,"…"]})})})]})}function ro({entry:a,jogakName:e,overrideArgs:t,onArgChange:r,onReset:j,onResolveJogak:n,viewport:g,bgMode:l,bottomTab:s,onViewportChange:c,onBgModeChange:i,onBottomTabChange:x,prismTheme:v,previewIsolation:w}){var L;const f=e??((L=a.jogaks[0])==null?void 0:L.name)??null;if(k.useEffect(()=>{e===null&&f!==null&&n!==void 0&&n(a.id,f)},[e,f,a.id,n]),f===null)return o.jsxs("div",{className:"jogak:p-6 jogak:text-[var(--jogak-color-error)]",children:["Entry has no jogaks: ",a.id]});const b=a.jogaks.find(N=>N.name===f);if(b===void 0)return o.jsxs("div",{className:"jogak:p-6 jogak:text-[var(--jogak-color-error)]",children:["Jogak not found: ",f]});const u={...b.args??{},...t},p={...a.meta.argTypes??{},...b.argTypes??{}},U=Object.keys(t).length>0,M=D[g];return o.jsxs("div",{className:"jogak:flex jogak:flex-col jogak:h-full",children:[o.jsx(H,{title:a.title,jogakName:b.name,viewport:g,bgMode:l,onViewportChange:c,onBgModeChange:i,showReset:U,onReset:j}),o.jsx("div",{className:`jogak:flex-1 jogak:overflow-auto jogak:min-h-[320px] ${$}`,style:E[l],children:o.jsx("div",{"data-jogak-content":!0,className:"jogak:mx-auto jogak:p-6 jogak:max-w-[var(--jogak-canvas-mw)]",style:{"--jogak-canvas-mw":M==="none"?"100%":`${M}px`},children:o.jsx(no,{entry:a,args:u,source:a.source,theme:v,previewIsolation:w},`${a.id}/${b.name}`)})}),o.jsxs("div",{"data-testid":"bottom-panel",className:"jogak:h-[260px] jogak:shrink-0 jogak:flex jogak:flex-col jogak:border-t-2 jogak:border-[var(--jogak-color-border)]",children:[o.jsx("div",{role:"tablist",className:"jogak:flex jogak:gap-1 jogak:pt-1 jogak:px-3 jogak:pb-0 jogak:bg-[var(--jogak-color-bg)] jogak:border-b jogak:border-[var(--jogak-color-border)] jogak:shrink-0",children:["controls","actions"].map(N=>{const O=s===N;return o.jsx("button",{type:"button",role:"tab","aria-selected":O,onClick:()=>{x(N)},className:h("jogak:px-[14px] jogak:py-[6px] jogak:text-[12px] jogak:bg-transparent jogak:border-x-0 jogak:border-t-0 jogak:border-b-2 jogak:border-solid jogak:-mb-px jogak:cursor-pointer jogak:capitalize",O?"jogak:font-semibold jogak:text-[var(--jogak-color-fg-strong)] jogak:border-[var(--jogak-color-accent)]":"jogak:font-medium jogak:text-[var(--jogak-color-fg-muted)] jogak:border-transparent"),children:N},N)})}),o.jsx("div",{className:"jogak:flex-1 jogak:min-h-0 jogak:overflow-auto",children:s==="controls"?o.jsx(W,{args:u,argTypes:p,onArgChange:r}):o.jsx(z,{})})]})]})}function H({title:a,jogakName:e,viewport:t,bgMode:r,onViewportChange:j,onBgModeChange:n,showReset:g,onReset:l}){return o.jsxs("div",{className:"jogak:flex jogak:items-center jogak:gap-[10px] jogak:px-[14px] jogak:py-[7px] jogak:border-b jogak:border-[var(--jogak-color-border)] jogak:bg-[var(--jogak-color-bg)] jogak:shrink-0",children:[o.jsxs("div",{className:"jogak:flex-1 jogak:text-[13px]",children:[o.jsx("span",{className:"jogak:text-[var(--jogak-color-fg-subtle)]",children:a}),o.jsx("span",{className:"jogak:text-[var(--jogak-color-border-strong)] jogak:mx-1.5 jogak:leading-none",children:"/"}),o.jsx("span",{className:"jogak:text-[var(--jogak-color-fg-strong)] jogak:font-semibold",children:e})]}),o.jsx("div",{className:"jogak:flex jogak:gap-0.5 jogak:bg-[var(--jogak-color-bg-subtle)] jogak:rounded-[var(--jogak-radius-lg)] jogak:p-0.5",children:["mobile","tablet","desktop"].map(s=>o.jsx("button",{type:"button",onClick:()=>{j(s)},"aria-pressed":t===s,className:h("jogak:px-[9px] jogak:py-[3px] jogak:text-[12px] jogak:border-none jogak:rounded-[var(--jogak-radius-md)] jogak:cursor-pointer jogak:transition-all jogak:duration-100",t===s?"jogak:bg-[var(--jogak-color-bg-elevated)] jogak:text-[var(--jogak-color-fg-strong)] jogak:font-semibold jogak:shadow-[0_1px_2px_rgba(0,0,0,0.08)]":"jogak:bg-transparent jogak:text-[var(--jogak-color-fg-muted)] jogak:font-normal jogak:shadow-none"),children:ao[s]},s))}),o.jsx("div",{className:"jogak:flex jogak:gap-1 jogak:items-center",children:["white","dark","transparent"].map(s=>o.jsx("button",{type:"button",onClick:()=>{n(s)},"aria-pressed":r===s,"aria-label":`${s} background`,className:h("jogak:w-5 jogak:h-5 jogak:rounded-[var(--jogak-radius-md)] jogak:border-2 jogak:cursor-pointer jogak:p-0 jogak:shrink-0",$,r===s?"jogak:border-[var(--jogak-color-accent)]":"jogak:border-[var(--jogak-color-border-strong)]"),style:E[s]},s))}),g&&o.jsx("button",{type:"button",onClick:l,className:"jogak:px-[10px] jogak:py-[3px] jogak:text-[12px] jogak:border jogak:border-[var(--jogak-color-border-strong)] jogak:rounded-[var(--jogak-radius-md)] jogak:bg-[var(--jogak-color-bg)] jogak:cursor-pointer jogak:text-[var(--jogak-color-fg)] jogak:leading-none",children:"Reset"})]})}function no({entry:a,args:e,source:t,theme:r,previewIsolation:j}){const[n,g]=k.useState(!1),l=o.jsxs("div",{className:"jogak:relative",children:[o.jsx(so,{entry:a,args:e,previewIsolation:j}),o.jsx("button",{type:"button",onClick:()=>{g(s=>!s)},"aria-pressed":n,"aria-label":n?"Hide source code":"Show source code",className:h("jogak:absolute jogak:bottom-2 jogak:right-2 jogak:px-[9px] jogak:py-1","jogak:text-[11px] jogak:font-[family-name:var(--jogak-font-mono)] jogak:font-semibold jogak:tracking-[0.02em]","jogak:text-[var(--jogak-color-bg)] jogak:border-none jogak:rounded-[5px] jogak:cursor-pointer","jogak:shadow-[0_1px_4px_rgba(0,0,0,0.2)] jogak:transition-[background-color] jogak:duration-150 jogak:leading-none",n?"jogak:bg-[var(--jogak-color-accent)]":"jogak:bg-[#1e293b]"),children:"</>"})]});return o.jsxs("div",{children:[l,n&&o.jsx("div",{className:"jogak:mt-2 jogak:rounded-[var(--jogak-radius-xl)] jogak:overflow-hidden jogak:h-[320px] jogak:shadow-[0_0_0_1px_rgba(0,0,0,0.08),_0_4px_16px_rgba(0,0,0,0.12)]",children:o.jsx(lo,{source:t,theme:r})})]})}const A="jogak:border jogak:border-dashed jogak:border-[var(--jogak-color-border)] jogak:rounded-[var(--jogak-radius-xl)] jogak:p-4 jogak:pb-9";function so({entry:a,args:e,previewIsolation:t}){return t==="shadow"?o.jsx(T,{"data-testid":"preview-content",className:A,children:o.jsx(jo,{entry:a,args:e})}):t==="iframe"?o.jsx(oo,{entry:a,args:e,"data-testid":"preview-content",className:`${A} jogak:block jogak:w-full jogak:bg-transparent jogak:min-h-[256px]`}):o.jsx(go,{entry:a,args:e})}function go({entry:a,args:e}){const t=k.useRef(null);return k.useEffect(()=>{const r=t.current;if(r!==null)return m.reactAdapter.render(a,e,r),()=>{queueMicrotask(()=>{m.reactAdapter.unmount(r)})}},[a]),k.useEffect(()=>{const r=t.current;r!==null&&m.reactAdapter.render(a,e,r)},[a,e]),o.jsx("div",{ref:t,"data-testid":"preview-content",className:A})}function jo({entry:a,args:e}){const t=k.useRef(null);return k.useEffect(()=>{const r=t.current;if(r!==null)return m.reactAdapter.render(a,e,r),()=>{queueMicrotask(()=>{m.reactAdapter.unmount(r)})}},[a]),k.useEffect(()=>{const r=t.current;r!==null&&m.reactAdapter.render(a,e,r)},[a,e]),o.jsx("div",{ref:t,"data-testid":"preview-content-shadow"})}function lo({source:a,theme:e}){const[t,r]=k.useState(!1),j=e.plain.backgroundColor??"#1e293b";if(a===void 0)return o.jsx("div",{className:"jogak:h-full jogak:flex jogak:items-center jogak:justify-center jogak:bg-[var(--jogak-source-bg)] jogak:text-[#94a3b8] jogak:text-[13px]",style:{"--jogak-source-bg":j},children:"Source not available"});const n=()=>{navigator.clipboard.writeText(a).then(()=>{r(!0),setTimeout(()=>{r(!1)},2e3)})};return o.jsxs("div",{className:"jogak:relative jogak:h-full",children:[o.jsx("button",{type:"button",onClick:n,className:"jogak:absolute jogak:top-[10px] jogak:right-3 jogak:z-[1] jogak:px-[9px] jogak:py-[3px] jogak:text-[11px] jogak:bg-[rgba(255,255,255,0.1)] jogak:text-[#e2e8f0] jogak:border jogak:border-[rgba(255,255,255,0.18)] jogak:rounded-[var(--jogak-radius-md)] jogak:cursor-pointer jogak:leading-none",children:t?"✓ Copied":"Copy"}),o.jsx(_.Highlight,{code:a.trim(),language:"tsx",theme:e,children:({style:g,tokens:l,getLineProps:s,getTokenProps:c})=>o.jsx("pre",{className:"jogak:m-0 jogak:py-3 jogak:px-0 jogak:text-[12.5px] jogak:leading-[1.7] jogak:font-[family-name:var(--jogak-font-mono)] jogak:h-full jogak:box-border jogak:overflow-auto",style:g,children:l.map((i,x)=>o.jsxs("div",{...s({line:i}),className:"jogak:flex jogak:pr-6",style:s({line:i}).style,children:[o.jsx("span",{className:"jogak:select-none jogak:min-w-10 jogak:pl-[14px] jogak:pr-[14px] jogak:text-right jogak:text-[rgba(148,163,184,0.45)] jogak:shrink-0 jogak:leading-[1.7]",children:x+1}),o.jsx("span",{children:i.map((v,w)=>o.jsx("span",{...c({token:v})},w))})]},x))})})]})}function P(){if(typeof window>"u")return null;const a=new URLSearchParams(window.location.search),e=a.get("entry");if(e===null)return null;const t=a.get("jogak");return{entryId:e,jogakName:t}}function ko(a,e){const t=new URLSearchParams;t.set("entry",a),t.set("jogak",e),window.history.pushState({},"",`?${t.toString()}`)}function co({entries:a,metas:e,codeTheme:t="vsDark",previewIsolation:r="shadow"}={}){const j=k.useMemo(()=>{if(a!==void 0){e!==void 0&&console.warn("[jogak] JogakApp received both `entries` and `metas` — `entries` (eager) takes precedence.");const d=new y.ComponentRegistry;for(const u of a)d.register(u);return d}if(e!==void 0)for(const d of e)y.defaultRegistry.registerMeta(d);return y.defaultRegistry},[a,e]),n=k.useMemo(()=>P(),[]),[g,l]=k.useState((n==null?void 0:n.entryId)??null),[s,c]=k.useState((n==null?void 0:n.jogakName)??null),[i,x]=k.useState({});k.useEffect(()=>{const d=()=>{const u=P();u!==null?(l(u.entryId),c(u.jogakName),x({})):(l(null),c(null))};return window.addEventListener("popstate",d),()=>{window.removeEventListener("popstate",d)}},[]);const v=k.useCallback((d,u)=>{l(d),c(u),x({}),ko(d,u)},[]),w=k.useCallback((d,u)=>{if(l(p=>p===d?d:p),c(p=>p??u),typeof window<"u"){const p=new URLSearchParams(window.location.search);p.get("entry")===d&&p.get("jogak")===null&&(p.set("jogak",u),window.history.replaceState({},"",`?${p.toString()}`))}},[]),f=k.useCallback((d,u)=>{x(p=>({...p,[d]:u}))},[]),b=k.useCallback(()=>{x({})},[]);return o.jsx(m.JogakProvider,{registry:j,children:o.jsxs("div",{"data-jogak-shell":!0,className:"jogak:grid jogak:grid-cols-[260px_1fr] jogak:h-dvh jogak:overflow-hidden",children:[o.jsx(q,{selectedEntryId:g,selectedJogakName:s,onSelect:v}),o.jsx("main",{className:"jogak:overflow-hidden jogak:min-h-0",children:g!==null?o.jsx(F,{entryId:g,jogakName:s,overrideArgs:i,onArgChange:f,onReset:b,codeTheme:t,onResolveJogak:w,previewIsolation:r}):o.jsx("div",{className:"jogak:flex jogak:items-center jogak:justify-center jogak:h-full jogak:text-[var(--jogak-color-fg-subtle)]",children:"Select a component from the sidebar"})})]})})}function io(){const a=m.useRegistry(),e=k.useMemo(()=>a.getAll(),[a]),t=k.useMemo(()=>a.getTree(),[a]),r=k.useMemo(()=>j=>a.search(j),[a]);return{entries:e,tree:t,search:r}}exports.Actions=z;exports.Controls=W;exports.JogakApp=co;exports.Preview=F;exports.Sidebar=q;exports.useRegistry=io;