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
28 changes: 12 additions & 16 deletions playwright/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ const waitForInitialRender = async (page: Page) => {
await expect(page.locator('#cdn-loading')).toHaveAttribute('hidden', '')
}

const expectPreviewHasRenderedContent = async (page: Page) => {
const previewHost = page.locator('#preview-host')
await expect(previewHost.locator('pre')).toHaveCount(0)
await expect
.poll(() => previewHost.evaluate(node => node.childElementCount))
.toBeGreaterThan(0)
}

const setComponentEditorSource = async (page: Page, source: string) => {
const editorContent = page.locator('.component-panel .cm-content').first()
await editorContent.fill(source)
Expand All @@ -26,10 +34,7 @@ test('renders default playground preview', async ({ page }) => {

await page.getByLabel('ShadowRoot (open)').uncheck()
await expect(page.locator('#status')).toHaveText('Rendered')

const previewItems = page.locator('#preview-host li')
await expect(previewItems).toHaveCount(3)
await expect(previewItems.first()).toContainText('apple')
await expectPreviewHasRenderedContent(page)
})

test('supports layout and theme toggles', async ({ page }) => {
Expand All @@ -56,10 +61,7 @@ test('renders in react mode with css modules', async ({ page }) => {
await page.locator('#render-mode').selectOption('react')
await page.locator('#style-mode').selectOption('module')
await expect(page.locator('#status')).toHaveText('Rendered')

const previewItems = page.locator('#preview-host li')
await expect(previewItems).toHaveCount(3)
await expect(previewItems.first()).toContainText('apple')
await expectPreviewHasRenderedContent(page)
})

test('transpiles TypeScript annotations in component source', async ({ page }) => {
Expand Down Expand Up @@ -156,10 +158,7 @@ test('renders with less style mode', async ({ page }) => {
await expect(page.locator('#style-warning')).toContainText(
'Less is compiled in-browser via @knighted/css/browser.',
)

const previewItems = page.locator('#preview-host li')
await expect(previewItems).toHaveCount(3)
await expect(previewItems.first()).toContainText('apple')
await expectPreviewHasRenderedContent(page)
})

test('renders with sass style mode', async ({ page }) => {
Expand All @@ -171,10 +170,7 @@ test('renders with sass style mode', async ({ page }) => {
await expect(page.locator('#style-warning')).toContainText(
'Sass is compiled in-browser via @knighted/css/browser.',
)

const previewItems = page.locator('#preview-host li')
await expect(previewItems).toHaveCount(3)
await expect(previewItems.first()).toContainText('apple')
await expectPreviewHasRenderedContent(page)
})

test('style compilation errors populate styles diagnostics scope', async ({ page }) => {
Expand Down
8 changes: 5 additions & 3 deletions scripts/build-prepare.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { cdnImportSpecs } from '../src/cdn.js'
import { cdnImportSpecs } from '../src/modules/cdn.js'

const validPrimaryCdns = new Set(['importMap', 'esm', 'jspmGa'])

Expand Down Expand Up @@ -49,12 +49,14 @@ const createProdImportsModule = async () => {
]

if (specifiers.length === 0) {
throw new Error('No importMap specifiers found in src/cdn.js (cdnImportSpecs).')
throw new Error(
'No importMap specifiers found in src/modules/cdn.js (cdnImportSpecs).',
)
}

const lines = [
'/*',
' * Generated by scripts/build-prepare.js from src/cdn.js (cdnImportSpecs).',
' * Generated by scripts/build-prepare.js from src/modules/cdn.js (cdnImportSpecs).',
' * JSPM links this module to trace top-level production imports.',
' */',
...specifiers.map(specifier => `import '${specifier}'`),
Expand Down
31 changes: 22 additions & 9 deletions src/app.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { cdnImports, getTypeScriptLibUrls, importFromCdnWithFallback } from './cdn.js'
import { createCodeMirrorEditor } from './editor-codemirror.js'
import { defaultCss, defaultJsx } from './defaults.js'
import { createDiagnosticsUiController } from './diagnostics-ui.js'
import { createLayoutThemeController } from './layout-theme.js'
import { createPreviewBackgroundController } from './preview-background.js'
import { createRenderRuntimeController } from './render-runtime.js'
import { createTypeDiagnosticsController } from './type-diagnostics.js'
import {
cdnImports,
getTypeScriptLibUrls,
importFromCdnWithFallback,
} from './modules/cdn.js'
import { createCodeMirrorEditor } from './modules/editor-codemirror.js'
import { defaultCss, defaultJsx, defaultReactJsx } from './modules/defaults.js'
import { createDiagnosticsUiController } from './modules/diagnostics-ui.js'
import { createLayoutThemeController } from './modules/layout-theme.js'
import { createPreviewBackgroundController } from './modules/preview-background.js'
import { createRenderRuntimeController } from './modules/render-runtime.js'
import { createTypeDiagnosticsController } from './modules/type-diagnostics.js'

const statusNode = document.getElementById('status')
const appGrid = document.querySelector('.app-grid')
Expand Down Expand Up @@ -48,6 +52,7 @@ let getCssSource = () => cssEditor.value
let renderRuntime = null
let pendingClearAction = null
let suppressEditorChangeSideEffects = false
let hasAppliedReactModeDefault = false
const clipboardSupported = Boolean(navigator.clipboard?.writeText)

const previewBackground = createPreviewBackgroundController({
Expand Down Expand Up @@ -346,7 +351,15 @@ const updateRenderButtonVisibility = () => {
renderButton.hidden = autoRenderToggle.checked
}

renderMode.addEventListener('change', maybeRender)
renderMode.addEventListener('change', () => {
if (renderMode.value === 'react' && !hasAppliedReactModeDefault) {
hasAppliedReactModeDefault = true
setJsxSource(defaultReactJsx)
markTypeDiagnosticsStale()
}

maybeRender()
})
styleMode.addEventListener('change', () => {
if (cssCodeEditor) {
cssCodeEditor.setLanguage(getStyleEditorLanguage(styleMode.value))
Expand Down
4 changes: 2 additions & 2 deletions src/bootstrap.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { getPrimaryCdnImportUrls } from './cdn.js'
import { getPrimaryCdnImportUrls } from './modules/cdn.js'

/*
* Preload only the modules needed for the initial render path.
* - Included: core runtime + React runtime modules used immediately.
* - Excluded: optional style compilers (sass/less/lightningCssWasm), which stay lazy.
* Keep this list aligned with cdnImports keys in cdn.js.
* Keep this list aligned with cdnImports keys in modules/cdn.js.
*/
const preloadImportKeys = [
'cssBrowser',
Expand Down
114 changes: 0 additions & 114 deletions src/defaults.js

This file was deleted.

File renamed without changes.
94 changes: 94 additions & 0 deletions src/modules/defaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
export const defaultJsx = [
'type CounterButtonProps = {',
' label: string',
' onClick: (event: MouseEvent) => void',
'}',
'',
'const CounterButton = ({ label, onClick }: CounterButtonProps) => (',
' <button id="counter-button" type="button" onClick={onClick}>',
' {label}',
' </button>',
')',
'',
'const App = () => {',
' let count = 0',
' const handleClick = (event: MouseEvent) => {',
' count += 1',
' const button = event.currentTarget as HTMLButtonElement',
' button.textContent = `Clicks: ${count}`',
" button.dataset.active = count % 2 === 0 ? 'false' : 'true'",
" button.classList.toggle('is-even', count % 2 === 0)",
' }',
'',
" return <CounterButton label='Clicks: 0' onClick={handleClick} />",
'}',
'',
].join('\n')

export const defaultReactJsx = [
'type CounterButtonProps = {',
' label: string',
' active: boolean',
' onClick: (event: MouseEvent) => void',
'}',
'',
'const CounterButton = ({ label, active, onClick }: CounterButtonProps) => (',
' <button',
' id="counter-button"',
' type="button"',
' data-active={active ? "true" : "false"}',
' className={active ? "is-even" : ""}',
' onClick={onClick}',
' >',
' {label}',
' </button>',
')',
'',
'const App = () => {',
' const { useState } = React',
' const [count, setCount] = useState(0)',
' const handleClick = (_event: MouseEvent) => {',
' setCount(current => current + 1)',
' }',
'',
' return (',
' <CounterButton',
' label={`React clicks: ${count}`}',
' active={count % 2 === 0}',
' onClick={handleClick}',
' />',
' )',
'}',
'',
].join('\n')

export const defaultCss = `#counter-button {
margin: 0;
padding: 0.75rem 1rem;
border: 1px solid #3558b8;
border-radius: 0.5rem;
background: #e9efff;
color: #1a2a52;
font-weight: 600;
cursor: pointer;
transition: background-color 120ms ease;
}

#counter-button:hover {
background: #dce6ff;
}

#counter-button[data-active='true'] {
background: #3558b8;
color: #fff;
}

#counter-button.is-even {
border-style: dashed;
}

#counter-button:focus-visible {
outline: 2px solid #6a84d8;
outline-offset: 2px;
}
`
10 changes: 9 additions & 1 deletion src/diagnostics-ui.js → src/modules/diagnostics-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export const createDiagnosticsUiController = ({
return 'pending'
}

if (diagnosticsByScope.component.level === 'ok') {
return 'ok'
}

return 'neutral'
}

Expand All @@ -67,6 +71,7 @@ export const createDiagnosticsUiController = ({
if (diagnosticsToggle) {
diagnosticsToggle.classList.remove(
'diagnostics-toggle--neutral',
'diagnostics-toggle--ok',
'diagnostics-toggle--pending',
'diagnostics-toggle--error',
)
Expand Down Expand Up @@ -108,7 +113,10 @@ export const createDiagnosticsUiController = ({

if (hasHeadline) {
const headingNode = document.createElement('div')
headingNode.className = 'type-diagnostics-heading'
headingNode.className =
state.level === 'ok'
? 'type-diagnostics-heading type-diagnostics-heading--ok'
: 'type-diagnostics-heading'
headingNode.textContent = state.headline
root.append(headingNode)
}
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading
Loading