diff --git a/frontend/components/studioBrand.ts b/frontend/components/studioBrand.ts index ccca7160..67abdcd0 100644 --- a/frontend/components/studioBrand.ts +++ b/frontend/components/studioBrand.ts @@ -1,7 +1,7 @@ import { type LogoConfig, type LogoSize, - resolveTsSsgContext, + resolvePageContext, } from '@purestack/ts-common' import { defineComponent, html, type RefOrValue } from 'regor' @@ -17,7 +17,7 @@ export function defineStudioBrandComponent() { props: ['size'], context: (head) => ({ ...head.props, - config: resolveTsSsgContext(head).site.logo, + config: resolvePageContext(head).site.logo, }), }) } diff --git a/frontend/purestack.studio/guides/extending/regor/_regor-frontmatter.mdx b/frontend/purestack.studio/guides/extending/regor/_regor-frontmatter.mdx new file mode 100644 index 00000000..fbae8ed5 --- /dev/null +++ b/frontend/purestack.studio/guides/extending/regor/_regor-frontmatter.mdx @@ -0,0 +1,9 @@ + +

From this page's frontmatter

+ {{ frontmatter.title }} + +
diff --git a/frontend/purestack.studio/guides/extending/regor/_regor-launch-checklist.mdx b/frontend/purestack.studio/guides/extending/regor/_regor-launch-checklist.mdx new file mode 100644 index 00000000..e1b2cb28 --- /dev/null +++ b/frontend/purestack.studio/guides/extending/regor/_regor-launch-checklist.mdx @@ -0,0 +1 @@ + diff --git a/frontend/purestack.studio/guides/extending/regor-launch-checklist.ts b/frontend/purestack.studio/guides/extending/regor/regor-launch-checklist.ts similarity index 100% rename from frontend/purestack.studio/guides/extending/regor-launch-checklist.ts rename to frontend/purestack.studio/guides/extending/regor/regor-launch-checklist.ts diff --git a/frontend/purestack.studio/guides/extending/regor.mdx b/frontend/purestack.studio/guides/extending/regor/regor.mdx similarity index 89% rename from frontend/purestack.studio/guides/extending/regor.mdx rename to frontend/purestack.studio/guides/extending/regor/regor.mdx index 1957b37f..0d68551e 100644 --- a/frontend/purestack.studio/guides/extending/regor.mdx +++ b/frontend/purestack.studio/guides/extending/regor/regor.mdx @@ -17,7 +17,13 @@ highlights: # Regor -Regor is the component framework PureStack is built on. It runs in two places: +Regor +is the component framework PureStack is built on. It runs in two places: @@ -153,7 +159,7 @@ Every page goes through the same four steps: | Where it runs | Your build, once per page | The visitor's browser | | What it produces | Static HTML | A live, interactive interface | | Event handlers, `r-model`, timers | Not kept | Run normally | -| Page data (`tsSsgContext`) | Available | Not available | +| Page data (`pageContext`) | Available | Not available | | Components | All built-in and registered components | The component families you register | ## Regor at build time @@ -185,45 +191,22 @@ A plain attribute passes a string. An attribute that starts with a colon is a Re ### Expressions and page data -Text in double braces is a Regor expression, anywhere in a page. Inside markup, expressions can read `tsSsgContext`, which holds the page and site data that PureStack provides during the build: +Text in double braces is a Regor expression, anywhere in a page. Inside markup, expressions can read `pageContext`, which holds the page and site data that PureStack provides during the build: | Field | Contents | | --- | --- | -| `tsSsgContext.pageInfo.frontmatter` | The page's frontmatter, including any custom fields you add | -| `tsSsgContext.pageInfo.urlPath` | The page's URL, such as `/guides/extending/regor/` | -| `tsSsgContext.pageInfo.relPath` | The source file path inside the content folder | -| `tsSsgContext.site` | The resolved site configuration | -| `tsSsgContext.navigation`, `tsSsgContext.outline` | The page's navigation and heading outline | -| `tsSsgContext.locale`, `tsSsgContext.locales` | The page's language and the site's languages | - -This page lists three highlights in its frontmatter. The panel below reads them during the build: - - -

From this page's frontmatter

- {{ tsSsgContext.pageInfo.frontmatter.title }} -
    -
  • - {{ item }} -
  • -
-
+| `pageContext.pageInfo.frontmatter` | The page's frontmatter, including any custom fields you add | +| `pageContext.pageInfo.urlPath` | The page's URL, such as `/guides/extending/regor/` | +| `pageContext.pageInfo.relPath` | The source file path inside the content folder | +| `pageContext.site` | The resolved site configuration | +| `pageContext.navigation`, `pageContext.outline` | The page's navigation and heading outline | +| `pageContext.locale`, `pageContext.locales` | The page's language and the site's languages | -```mdx ---- -title: Regor -highlights: - - One component model for pages and apps - - Plain TypeScript, no compiler plugin - - Built-in components and theme styles in both places ---- +This page lists three highlights in its frontmatter. The panel below reads them during the build. `:context="pageContext.pageInfo"` makes `frontmatter` available directly inside the panel: - - {{ tsSsgContext.pageInfo.frontmatter.title }} -
    -
  • {{ item }}
  • -
-
-``` + + + `r-if`, `r-else-if`, and `r-else` choose what to render. `r-for` repeats an element for each item. Loop over data from frontmatter, the page context, or component state. @@ -294,9 +277,10 @@ export default defineConfig({ > Export the interface that types a component, as ReleaseNote is exported here. The - VS Code extension finds components - through their exported interfaces. Without the export, it can't complete the - component's props or jump from a tag in your markup to the component's source. + VS Code extension + finds components through their exported interfaces. Without the export, it + can't complete the component's props or jump from a tag in your markup to the + component's source. Then use it in any page. Content between the tags fills the ``: @@ -307,7 +291,7 @@ Then use it in any page. Content between the tags fills the ``: ``` -Tag names match registered keys regardless of case, so `` finds `releaseNote`. The kebab-case form `` works too. Components can also read page data. Pass the component head to `resolveTsSsgContext` from `@purestack/ts-common`, as the built-in navigation and page-link components do. +Tag names match registered keys regardless of case, so `` finds `releaseNote`. The kebab-case form `` works too. Components can also read page data. Pass the component head to `resolvePageContext` from `@purestack/ts-common`, as the built-in navigation and page-link components do. ## Regor in the browser @@ -315,11 +299,9 @@ Tag names match registered keys regardless of case, so `` finds `re Place a `RegorApp` in a page and point it at a TypeScript file next to the page. It renders an `app` element with the `id` you give it and loads the script. The script calls `createApp` with the matching selector: - + -```mdx - -``` + @@ -332,8 +314,8 @@ Browser apps do not get the build's component list. Each app registers the compo Register each icon your app shows, including the defaults that components draw themselves, such as the arrow in a select field. The - Icons guide lists those - defaults. + Icons guide lists + those defaults. ### Template syntax @@ -483,7 +465,7 @@ These patterns scale from a single widget to a multi-page admin tool with tables ## Use other libraries -A [PageScript](/components/runtime/page-script/) loads a browser entry point. Use it to enhance existing HTML or mount a browser-compatible library. PureStack bundles local TypeScript and its imports with esbuild; framework-specific source formats may need an additional compilation step. Start with a plain TypeScript module, as shown in [runtime scripts](../../components/runtime). +A [PageScript](/components/runtime/page-script/) loads a browser entry point. Use it to enhance existing HTML or mount a browser-compatible library. PureStack bundles local TypeScript and its imports with esbuild; framework-specific source formats may need an additional compilation step. Start with a plain TypeScript module, as shown in [runtime scripts](../../../components/runtime). Other frameworks can't use the built-in components, and they don't inherit the diff --git a/package.json b/package.json index e34edf64..80b78c74 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,18 @@ { "private": true, "name": "purestack-repo", - "version": "1.1.8", + "version": "1.1.9", "packageManager": "yarn@4.18.1", "type": "module", "sideEffects": false, "workspaces": [ "packages/*" ], + "dependencyConsistency": { + "exceptions": { + "typescript": "The repository uses TypeScript 7 for tsc; the VS Code extension needs TypeScript 6's JavaScript compiler API." + } + }, "license": "MIT", "repository": { "type": "git", @@ -43,6 +48,7 @@ "lint": "yarn workspaces foreach --no-private --all --verbose --topological -- run lint", "lint:ci": "yarn workspaces foreach --no-private --all --verbose --topological -- exec biome check ./src --formatter-enabled=false", "check-eol": "yarn tsx scripts/check-eol.ts", + "check-dependencies": "yarn tsx scripts/check-dependencies.ts", "bump-version": "yarn tsx --no-warnings scripts/bump-version.ts", "bundle": "yarn tsx --no-warnings scripts/bundle.ts", "cleanup": "yarn tsx --no-warnings scripts/cleanup.ts", @@ -53,6 +59,6 @@ "pack-vscode-ext": "yarn --cwd packages/ts-ssg-vscode build && yarn --cwd packages/ts-ssg-vscode package", "longest": "yarn tsx ./scripts/longest-functions.ts", "format": "biome format --write", - "pre-commit": "yarn check-eol && yarn regor-components && yarn embed && yarn format && yarn lint && yarn build && yarn test run" + "pre-commit": "yarn check-dependencies && yarn check-eol && yarn regor-components && yarn embed && yarn format && yarn lint && yarn build && yarn test run" } } diff --git a/packages/purestack/package.json b/packages/purestack/package.json index 7a9cff36..428c2f77 100644 --- a/packages/purestack/package.json +++ b/packages/purestack/package.json @@ -1,6 +1,6 @@ { "name": "purestack", - "version": "1.1.8", + "version": "1.1.9", "packageManager": "yarn@4.9.2", "module": "./dist/purestack.mjs", "types": "./dist/purestack.d.mts", diff --git a/packages/purestack/src/index.ts b/packages/purestack/src/index.ts index de6775e8..cf19fe12 100644 --- a/packages/purestack/src/index.ts +++ b/packages/purestack/src/index.ts @@ -1,3 +1,3 @@ -export const version: string = '1.1.8' +export const version: string = '1.1.9' export * from '@purestack/ts-ssg' diff --git a/packages/ts-common/README.md b/packages/ts-common/README.md index e7a287aa..855951f2 100644 --- a/packages/ts-common/README.md +++ b/packages/ts-common/README.md @@ -5,6 +5,6 @@ scripts. This package defines site configuration, page frontmatter, navigation, logos, templates, and the rendering context so those packages agree on the same shapes. -Import types such as `SiteConfig`, `PageFrontmatter`, and `TsSsgContext` when -building a custom integration. The runtime exports `resolveTsSsgContext` and -`tryResolveTsSsgContext` find the site context in a Regor component. +Import types such as `SiteConfig`, `PageFrontmatter`, and `PageContext` when +building a custom integration. The runtime exports `resolvePageContext` and +`tryResolvePageContext` find the site context in a Regor component. diff --git a/packages/ts-common/package.json b/packages/ts-common/package.json index 3f450c88..cf766b98 100644 --- a/packages/ts-common/package.json +++ b/packages/ts-common/package.json @@ -1,6 +1,6 @@ { "name": "@purestack/ts-common", - "version": "1.1.8", + "version": "1.1.9", "packageManager": "yarn@4.9.2", "module": "./dist/ts-common.mjs", "types": "./dist/ts-common.d.mts", diff --git a/packages/ts-common/src/index.ts b/packages/ts-common/src/index.ts index 14ae61d9..1de3a185 100644 --- a/packages/ts-common/src/index.ts +++ b/packages/ts-common/src/index.ts @@ -39,9 +39,9 @@ export type { PageTranslationInfo, } from './page-template-types' export { - resolveTsSsgContext, - tryResolveTsSsgContext, -} from './resolveTsSsgContext' + resolvePageContext, + tryResolvePageContext, +} from './resolvePageContext' export type { AnalyticsConfig, AuthConfig, @@ -63,7 +63,7 @@ export type { SiteStyleConfigInput, } from './site-config-types' export type { + PageContext, PageOutlineItem, - TsSsgContext, TsSsgNavigation, } from './ts-ssg-context' diff --git a/packages/ts-common/src/resolvePageContext.ts b/packages/ts-common/src/resolvePageContext.ts new file mode 100644 index 00000000..8ada1dd7 --- /dev/null +++ b/packages/ts-common/src/resolvePageContext.ts @@ -0,0 +1,35 @@ +import type { PageContext } from './ts-ssg-context' + +type ContextCarrier = { + pageContext?: PageContext +} + +type HeadLike = { + ctx?: unknown[] +} + +function hasPageContext(value: unknown): value is ContextCarrier { + return typeof value === 'object' && value !== null && 'pageContext' in value +} + +function hasContextStack(value: unknown): value is HeadLike { + return typeof value === 'object' && value !== null && 'ctx' in value +} + +export function resolvePageContext(head: unknown): PageContext { + const stack = hasContextStack(head) && Array.isArray(head.ctx) ? head.ctx : [] + for (const ctx of stack) { + if (!hasPageContext(ctx)) continue + if (ctx.pageContext) return ctx.pageContext + } + throw new Error('pageContext is not available in the Regor context stack.') +} + +export function tryResolvePageContext(head: unknown): PageContext | undefined { + const stack = hasContextStack(head) && Array.isArray(head.ctx) ? head.ctx : [] + for (const ctx of stack) { + if (!hasPageContext(ctx)) continue + if (ctx.pageContext) return ctx.pageContext + } + return undefined +} diff --git a/packages/ts-common/src/resolveTsSsgContext.ts b/packages/ts-common/src/resolveTsSsgContext.ts deleted file mode 100644 index e6c5a95a..00000000 --- a/packages/ts-common/src/resolveTsSsgContext.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { TsSsgContext } from './ts-ssg-context' - -type ContextCarrier = { - tsSsgContext?: TsSsgContext -} - -type HeadLike = { - ctx?: unknown[] -} - -function hasTsSsgContext(value: unknown): value is ContextCarrier { - return typeof value === 'object' && value !== null && 'tsSsgContext' in value -} - -function hasContextStack(value: unknown): value is HeadLike { - return typeof value === 'object' && value !== null && 'ctx' in value -} - -export function resolveTsSsgContext(head: unknown): TsSsgContext { - const stack = hasContextStack(head) && Array.isArray(head.ctx) ? head.ctx : [] - for (const ctx of stack) { - if (!hasTsSsgContext(ctx)) continue - if (ctx.tsSsgContext) return ctx.tsSsgContext - } - throw new Error('tsSsgContext is not available in the Regor context stack.') -} - -export function tryResolveTsSsgContext( - head: unknown, -): TsSsgContext | undefined { - const stack = hasContextStack(head) && Array.isArray(head.ctx) ? head.ctx : [] - for (const ctx of stack) { - if (!hasTsSsgContext(ctx)) continue - if (ctx.tsSsgContext) return ctx.tsSsgContext - } - return undefined -} diff --git a/packages/ts-common/src/ts-ssg-context.ts b/packages/ts-common/src/ts-ssg-context.ts index 537c7fc2..d9c4c242 100644 --- a/packages/ts-common/src/ts-ssg-context.ts +++ b/packages/ts-common/src/ts-ssg-context.ts @@ -21,7 +21,7 @@ export interface TsSsgNavigation { pageLinks?: PageNavigationLinks } -export interface TsSsgContext { +export interface PageContext { site: SiteConfig pageInfo: PageInfo navigation?: TsSsgNavigation diff --git a/packages/ts-components/package.json b/packages/ts-components/package.json index e1c5a068..88af964c 100644 --- a/packages/ts-components/package.json +++ b/packages/ts-components/package.json @@ -1,6 +1,6 @@ { "name": "@purestack/ts-components", - "version": "1.1.8", + "version": "1.1.9", "packageManager": "yarn@4.9.2", "module": "./dist/ts-components.mjs", "types": "./dist/ts-components.d.mts", @@ -55,7 +55,7 @@ "@purestack/ts-page-scripts": "workspace:^", "@purestack/ts-style": "workspace:^", "@purestack/ts-util": "workspace:^", - "regor": "^1.7.3" + "regor": "^1.7.4" }, "regorComponents": [ "AlertBox", diff --git a/packages/ts-components/src/standard/barChart/barChart.test.ts b/packages/ts-components/src/standard/barChart/barChart.test.ts index 3ea785ec..03d06454 100644 --- a/packages/ts-components/src/standard/barChart/barChart.test.ts +++ b/packages/ts-components/src/standard/barChart/barChart.test.ts @@ -17,7 +17,7 @@ describe('BarChart rendering', () => { const app = createApp( { components: defineBarChartComponents(), - tsSsgContext: createTestContext(), + pageContext: createTestContext(), items: values.map((value, index) => ({ label: `Day ${index + 1}`, value, @@ -57,7 +57,7 @@ describe('BarChart rendering', () => { const app = createApp( { components: defineBarChartComponents(), - tsSsgContext: createTestContext(), + pageContext: createTestContext(), items: [ { label: 'Core', value: 42, color: '#18a999' }, { label: 'Edge', value: 28, color: '#f4a261' }, @@ -102,7 +102,7 @@ describe('BarChart rendering', () => { const app = createApp( { components: defineBarChartComponents(), - tsSsgContext: createTestContext(), + pageContext: createTestContext(), items: [{ label: 'Invalid', value: Number.NaN }], }, { @@ -131,7 +131,7 @@ describe('BarChart rendering', () => { const app = createApp( { components: defineBarChartComponents(), - tsSsgContext: createTestContext(), + pageContext: createTestContext(), items: [{ label: 'Core', value: 42 }], }, { diff --git a/packages/ts-components/src/standard/btn/btn.ts b/packages/ts-components/src/standard/btn/btn.ts index dbd58e94..1cde5c35 100644 --- a/packages/ts-components/src/standard/btn/btn.ts +++ b/packages/ts-components/src/standard/btn/btn.ts @@ -1,4 +1,4 @@ -import { type TsSsgContext, tryResolveTsSsgContext } from '@purestack/ts-common' +import { type PageContext, tryResolvePageContext } from '@purestack/ts-common' import type { SemanticTone } from '@purestack/ts-style' import { type ComputedRef, @@ -104,7 +104,7 @@ function defineButtonLinkComponent() { 'iconOnly', 'ariaLabel', ], - context: (head) => resolveBtnLink(head.props, tryResolveTsSsgContext(head)), + context: (head) => resolveBtnLink(head.props, tryResolvePageContext(head)), }) } @@ -132,7 +132,7 @@ function resolveBtn(props: Btn): Btn { function resolveBtnLink( props: BtnLink, - context: TsSsgContext | undefined, + context: PageContext | undefined, ): BtnLink { return { ...props, @@ -176,7 +176,7 @@ function resolveShowEndIcon(props: BtnBase) { return hasIcon && unref(props.iconPosition) === 'end' } -function resolveButtonHref(props: BtnLink, context: TsSsgContext | undefined) { +function resolveButtonHref(props: BtnLink, context: PageContext | undefined) { const value = unref(props.href) const href = typeof value === 'string' ? value.trim() : undefined return href ? (context?.resolvePublicHref(href) ?? href) : undefined diff --git a/packages/ts-components/src/standard/classicLogo/classicLogo.ts b/packages/ts-components/src/standard/classicLogo/classicLogo.ts index 1ff498a6..b05e9443 100644 --- a/packages/ts-components/src/standard/classicLogo/classicLogo.ts +++ b/packages/ts-components/src/standard/classicLogo/classicLogo.ts @@ -1,7 +1,7 @@ import { type ClassicLogoConfig, - type TsSsgContext, - tryResolveTsSsgContext, + type PageContext, + tryResolvePageContext, } from '@purestack/ts-common' import { getCurrentThemePaletteVar, @@ -19,8 +19,16 @@ import { } from 'regor' const logoTemplate = html`