Skip to content

Commit 4592c2e

Browse files
feat: better knighted css loading.
1 parent 2211002 commit 4592c2e

4 files changed

Lines changed: 395 additions & 3 deletions

File tree

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,27 @@ browser acts as the runtime host for editing, render, lint, and typecheck flows.
3636
- use AI chat with tab-aware proposals and apply/undo controls
3737
- switch theme and collapse the preview panel while preserving fast feedback loops
3838

39+
## CSS Query Imports In Editor Tabs
40+
41+
`@knighted/develop` supports `@knighted/css` query syntax in workspace tabs, including:
42+
43+
- `./styles/button.module.css?knighted-css`
44+
- `./button.tsx?knighted-css&combined`
45+
46+
For Lit + React-in-Shadow-DOM flows, a minimal pattern is:
47+
48+
1. In `button.tsx`, export your React component and import CSS Modules normally.
49+
2. In `lit-host.ts`, import from `./button.tsx?knighted-css&combined` and use `knightedCss`.
50+
3. Apply `unsafeCSS(knightedCss)` in `static styles` so styles render inside the shadow root.
51+
52+
Example imports:
53+
54+
- `import { ReactButton, knightedCss } from './button.tsx?knighted-css&combined'`
55+
- `import { LitElement, css, html, unsafeCSS } from 'lit'`
56+
57+
`knightedCssModules` is optional for this flow and is not required when you only need
58+
the compiled CSS text plus your component exports.
59+
3960
## Why this shape
4061

4162
The app started as a focused compile-and-preview loop and has grown into a

playwright/rendering-modes/core.spec.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,128 @@ test('css module imports expose class map for module tabs', async ({ page }) =>
369369
.not.toContain('.item:active')
370370
})
371371

372+
test('workspace modules support ?knighted-css&combined imports', async ({ page }) => {
373+
await waitForInitialRender(page)
374+
375+
await ensurePanelToolsVisible(page, 'component')
376+
await ensurePanelToolsVisible(page, 'styles')
377+
378+
await page.getByRole('button', { name: 'Open tab App.tsx' }).click()
379+
await page.getByRole('combobox', { name: 'Render mode' }).selectOption('react')
380+
381+
await renameWorkspaceTab(page, {
382+
from: 'app.css',
383+
to: 'button.module.css',
384+
})
385+
386+
await page.getByRole('button', { name: 'Open tab button.module.css' }).click()
387+
await page.getByRole('combobox', { name: 'Style mode' }).selectOption('module')
388+
389+
await setWorkspaceTabSource(page, {
390+
fileName: 'button.module.css',
391+
kind: 'styles',
392+
source: ['.btn {', ' color: rgb(7, 89, 160);', ' font-weight: 700;', '}'].join(
393+
'\n',
394+
),
395+
})
396+
397+
await addWorkspaceTab(page, { type: 'script' })
398+
await renameWorkspaceTab(page, {
399+
from: 'module.tsx',
400+
to: 'button.tsx',
401+
})
402+
403+
await setWorkspaceTabSource(page, {
404+
fileName: 'button.tsx',
405+
source: [
406+
"import styles from '../styles/button.module.css'",
407+
'',
408+
'type ButtonProps = {',
409+
' label: string',
410+
'}',
411+
'',
412+
'export const ReactButton = ({ label }: ButtonProps) => (',
413+
' <button type="button" className={styles.btn}>{label}</button>',
414+
')',
415+
].join('\n'),
416+
})
417+
418+
await setComponentEditorSource(
419+
page,
420+
[
421+
"import { ReactButton, knightedCss } from './button.tsx?knighted-css&combined'",
422+
'',
423+
'const App = () => (',
424+
' <>',
425+
' <style>{knightedCss}</style>',
426+
' <ReactButton label="Combined query works" />',
427+
' </>',
428+
')',
429+
].join('\n'),
430+
)
431+
432+
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Rendered')
433+
await expect(page.locator('#preview-host pre.preview-runtime-error')).toHaveCount(0)
434+
await expect(
435+
getPreviewFrame(page).getByRole('button', { name: 'Combined query works' }),
436+
).toHaveCSS('color', 'rgb(7, 89, 160)')
437+
})
438+
439+
test('workspace style tabs support ?knighted-css query exports', async ({ page }) => {
440+
await waitForInitialRender(page)
441+
442+
await ensurePanelToolsVisible(page, 'component')
443+
await ensurePanelToolsVisible(page, 'styles')
444+
445+
await page.getByRole('button', { name: 'Open tab app.css' }).click()
446+
await renameWorkspaceTab(page, {
447+
from: 'app.css',
448+
to: 'button.module.css',
449+
})
450+
await page.getByRole('combobox', { name: 'Style mode' }).selectOption('module')
451+
452+
await setWorkspaceTabSource(page, {
453+
fileName: 'button.module.css',
454+
kind: 'styles',
455+
source: [
456+
'.btn {',
457+
' color: rgb(14, 110, 173);',
458+
' border: 1px solid rgb(14, 110, 173);',
459+
'}',
460+
].join('\n'),
461+
})
462+
463+
await page.getByRole('button', { name: 'Open tab App.tsx' }).click()
464+
await setComponentEditorSource(
465+
page,
466+
[
467+
"import styles, { knightedCss } from '../styles/button.module.css?knighted-css'",
468+
'',
469+
'const App = () => (',
470+
' <>',
471+
' <style>{knightedCss}</style>',
472+
' <button',
473+
' type="button"',
474+
' className={styles.btn}',
475+
' data-css-length={String(knightedCss.length)}',
476+
' >',
477+
' Style query works',
478+
' </button>',
479+
' </>',
480+
')',
481+
].join('\n'),
482+
)
483+
484+
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Rendered')
485+
await expect(page.locator('#preview-host pre.preview-runtime-error')).toHaveCount(0)
486+
487+
const previewButton = getPreviewFrame(page).getByRole('button', {
488+
name: 'Style query works',
489+
})
490+
await expect(previewButton).toHaveCSS('color', 'rgb(14, 110, 173)')
491+
await expect(previewButton).toHaveAttribute('data-css-length', /[1-9]\d*/)
492+
})
493+
372494
test('preview styles require explicit import from entry graph', async ({ page }) => {
373495
await waitForInitialRender(page)
374496

0 commit comments

Comments
 (0)