@prairielearn/preact 1.0.7 → 1.1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @prairielearn/preact
2
2
 
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e2bffd9: Prefer `className` instead of `class`
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [e2bffd9]
12
+ - @prairielearn/preact-cjs@2.0.0
13
+
3
14
  ## 1.0.7
4
15
 
5
16
  ### Patch Changes
package/dist/server.d.ts CHANGED
@@ -15,7 +15,7 @@ interface HydrateProps {
15
15
  /** Whether to apply full height styles. */
16
16
  fullHeight?: boolean;
17
17
  /** Optional CSS class to apply to the container. */
18
- class?: string;
18
+ className?: string;
19
19
  }
20
20
  /**
21
21
  * A component that renders a Preact component for client-side hydration.
@@ -23,7 +23,7 @@ interface HydrateProps {
23
23
  * This component is intended to be used within a non-interactive Preact component
24
24
  * that will be rendered without hydration through `renderHtml`.
25
25
  */
26
- export declare function Hydrate({ children, nameOverride, class: className, fullHeight, }: HydrateProps): VNode;
26
+ export declare function Hydrate({ children, nameOverride, className, fullHeight, }: HydrateProps): VNode;
27
27
  /**
28
28
  * Renders a Preact component for client-side hydration and returns an HTML-safe string.
29
29
  * This function is intended to be used within a tagged template literal, e.g. html`...`.
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,KAAK,iBAAiB,EAAY,KAAK,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAwBxF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,KAAK,UAEhD;AAED,UAAU,YAAY;IACpB,gCAAgC;IAChC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,EACtB,QAAQ,EACR,YAAY,EACZ,KAAK,EAAE,SAAS,EAChB,UAAkB,GACnB,EAAE,YAAY,GAAG,KAAK,CAgFtB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAC3B,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EACjB,KAAK,GAAE,IAAI,CAAC,YAAY,EAAE,UAAU,CAAM,GACzC,cAAc,CAGhB"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,KAAK,iBAAiB,EAAY,KAAK,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAwBxF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,KAAK,UAEhD;AAED,UAAU,YAAY;IACpB,gCAAgC;IAChC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,EACtB,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,UAAkB,GACnB,EAAE,YAAY,GAAG,KAAK,CAgFtB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAC3B,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EACjB,KAAK,GAAE,IAAI,CAAC,YAAY,EAAE,UAAU,CAAM,GACzC,cAAc,CAGhB"}
package/dist/server.js CHANGED
@@ -40,7 +40,7 @@ export function renderHtmlDocument(content) {
40
40
  * This component is intended to be used within a non-interactive Preact component
41
41
  * that will be rendered without hydration through `renderHtml`.
42
42
  */
43
- export function Hydrate({ children, nameOverride, class: className, fullHeight = false, }) {
43
+ export function Hydrate({ children, nameOverride, className, fullHeight = false, }) {
44
44
  if (!isValidElement(children)) {
45
45
  throw new Error('<Hydrate> expects a single Preact component as its child');
46
46
  }
@@ -95,7 +95,7 @@ registerHydratedComponent(${componentName});</code></pre>
95
95
  // eslint-disable-next-line @eslint-react/dom/no-dangerously-set-innerhtml
96
96
  dangerouslySetInnerHTML: {
97
97
  __html: escapeJsonForHtml(props),
98
- }, "data-component": componentName, "data-component-props": true }), _jsx("div", { "data-component": componentName, class: clsx('js-hydrated-component', { 'h-100': fullHeight }, className), children: _jsx(Component, { ...props }) })] }));
98
+ }, "data-component": componentName, "data-component-props": true }), _jsx("div", { "data-component": componentName, className: clsx('js-hydrated-component', { 'h-100': fullHeight }, className), children: _jsx(Component, { ...props }) })] }));
99
99
  }
100
100
  /**
101
101
  * Renders a Preact component for client-side hydration and returns an HTML-safe string.
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.tsx"],"names":[],"mappings":"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","sourcesContent":["import clsx from 'clsx';\nimport { isFragment, isValidElement } from 'preact/compat';\nimport superjson from 'superjson';\n\nimport { compiledScriptPath, compiledScriptPreloadPaths } from '@prairielearn/compiled-assets';\nimport { AugmentedError } from '@prairielearn/error';\nimport { type HtmlSafeString, html } from '@prairielearn/html';\nimport { type ComponentChildren, Fragment, type VNode } from '@prairielearn/preact-cjs';\n\nimport { renderHtml } from './index.js';\n\n// Based on https://pkg.go.dev/encoding/json#HTMLEscape\nconst ENCODE_HTML_RULES: Record<string, string> = {\n '&': '\\\\u0026',\n '>': '\\\\u003e',\n '<': '\\\\u003c',\n '\\u2028': '\\\\u2028',\n '\\u2029': '\\\\u2029',\n};\nconst MATCH_HTML = /[&><\\u2028\\u2029]/g;\n\n/**\n * Escape a value for use in a JSON string that will be rendered in HTML.\n *\n * @param value - The value to escape.\n * @returns A JSON string with HTML-sensitive characters escaped.\n */\nfunction escapeJsonForHtml(value: any): string {\n return superjson.stringify(value).replaceAll(MATCH_HTML, (c) => ENCODE_HTML_RULES[c] || c);\n}\n\n/**\n * Render an entire Preact page as an HTML document.\n *\n * @param content - A Preact VNode to render to HTML.\n * @returns An HTML string containing the rendered content.\n */\nexport function renderHtmlDocument(content: VNode) {\n return `<!doctype html>\\n${renderHtml(content)}`;\n}\n\ninterface HydrateProps {\n /** The component to hydrate. */\n children: ComponentChildren;\n /** Optional override for the component's name or displayName. */\n nameOverride?: string;\n /** Whether to apply full height styles. */\n fullHeight?: boolean;\n /** Optional CSS class to apply to the container. */\n class?: string;\n}\n\n/**\n * A component that renders a Preact component for client-side hydration.\n * All interactive components will need to be hydrated.\n * This component is intended to be used within a non-interactive Preact component\n * that will be rendered without hydration through `renderHtml`.\n */\nexport function Hydrate({\n children,\n nameOverride,\n class: className,\n fullHeight = false,\n}: HydrateProps): VNode {\n if (!isValidElement(children)) {\n throw new Error('<Hydrate> expects a single Preact component as its child');\n }\n\n if (isFragment(children)) {\n throw new Error('<Hydrate> does not support fragments');\n }\n\n const content = children as VNode;\n const { type: Component, props } = content;\n if (typeof Component !== 'function') {\n throw new Error('<Hydrate> expects a Preact component');\n }\n\n // Note that we don't use `Component.name` here because it can be minified or mangled.\n const componentName = nameOverride ?? Component.displayName;\n if (!componentName) {\n // This is only defined in development, not in production when the function name is minified.\n const componentDevName = Component.name || 'UnknownComponent';\n throw new AugmentedError(\n '<Hydrate> expects a component to have a displayName or nameOverride.',\n {\n info: html`\n <div>\n <p>Make sure to add a displayName to the component:</p>\n <pre><code>export const ${componentDevName} = ...;\n// Add this line:\n${componentDevName}.displayName = '${componentDevName}';</code></pre>\n </div>\n `,\n },\n );\n }\n\n const scriptPath = `esm-bundles/hydrated-components/${componentName}.ts`;\n let compiledScriptSrc = '';\n try {\n compiledScriptSrc = compiledScriptPath(scriptPath);\n } catch (error) {\n throw new AugmentedError(`Could not find script for component \"${componentName}\".`, {\n info: html`\n <div>\n Make sure you create a script at\n <code>esm-bundles/hydrated-components/${componentName}.ts</code> registering the\n component:\n <pre><code>import { registerHydratedComponent } from '@prairielearn/preact/hydrated-component';\n\nimport { ${componentName} } from './path/to/component.js';\n\nregisterHydratedComponent(${componentName});</code></pre>\n </div>\n `,\n cause: error,\n });\n }\n const scriptPreloads = compiledScriptPreloadPaths(scriptPath);\n return (\n <Fragment>\n <script type=\"module\" src={compiledScriptSrc} />\n {scriptPreloads.map((preloadPath) => (\n <link key={preloadPath} rel=\"modulepreload\" href={preloadPath} />\n ))}\n <script\n type=\"application/json\"\n // eslint-disable-next-line @eslint-react/dom/no-dangerously-set-innerhtml\n dangerouslySetInnerHTML={{\n __html: escapeJsonForHtml(props),\n }}\n data-component={componentName}\n data-component-props\n />\n <div\n data-component={componentName}\n class={clsx('js-hydrated-component', { 'h-100': fullHeight }, className)}\n >\n <Component {...props} />\n </div>\n </Fragment>\n );\n}\n\n/**\n * Renders a Preact component for client-side hydration and returns an HTML-safe string.\n * This function is intended to be used within a tagged template literal, e.g. html`...`.\n *\n * @param content - A Preact VNode to render to HTML.\n * @returns An `HtmlSafeString` containing the rendered HTML.\n */\nexport function hydrateHtml<T>(\n content: VNode<T>,\n props: Omit<HydrateProps, 'children'> = {},\n): HtmlSafeString {\n // Useful for adding Preact components to existing tagged-template pages.\n return renderHtml(<Hydrate {...props}>{content}</Hydrate>);\n}\n"]}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.tsx"],"names":[],"mappings":"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","sourcesContent":["import clsx from 'clsx';\nimport { isFragment, isValidElement } from 'preact/compat';\nimport superjson from 'superjson';\n\nimport { compiledScriptPath, compiledScriptPreloadPaths } from '@prairielearn/compiled-assets';\nimport { AugmentedError } from '@prairielearn/error';\nimport { type HtmlSafeString, html } from '@prairielearn/html';\nimport { type ComponentChildren, Fragment, type VNode } from '@prairielearn/preact-cjs';\n\nimport { renderHtml } from './index.js';\n\n// Based on https://pkg.go.dev/encoding/json#HTMLEscape\nconst ENCODE_HTML_RULES: Record<string, string> = {\n '&': '\\\\u0026',\n '>': '\\\\u003e',\n '<': '\\\\u003c',\n '\\u2028': '\\\\u2028',\n '\\u2029': '\\\\u2029',\n};\nconst MATCH_HTML = /[&><\\u2028\\u2029]/g;\n\n/**\n * Escape a value for use in a JSON string that will be rendered in HTML.\n *\n * @param value - The value to escape.\n * @returns A JSON string with HTML-sensitive characters escaped.\n */\nfunction escapeJsonForHtml(value: any): string {\n return superjson.stringify(value).replaceAll(MATCH_HTML, (c) => ENCODE_HTML_RULES[c] || c);\n}\n\n/**\n * Render an entire Preact page as an HTML document.\n *\n * @param content - A Preact VNode to render to HTML.\n * @returns An HTML string containing the rendered content.\n */\nexport function renderHtmlDocument(content: VNode) {\n return `<!doctype html>\\n${renderHtml(content)}`;\n}\n\ninterface HydrateProps {\n /** The component to hydrate. */\n children: ComponentChildren;\n /** Optional override for the component's name or displayName. */\n nameOverride?: string;\n /** Whether to apply full height styles. */\n fullHeight?: boolean;\n /** Optional CSS class to apply to the container. */\n className?: string;\n}\n\n/**\n * A component that renders a Preact component for client-side hydration.\n * All interactive components will need to be hydrated.\n * This component is intended to be used within a non-interactive Preact component\n * that will be rendered without hydration through `renderHtml`.\n */\nexport function Hydrate({\n children,\n nameOverride,\n className,\n fullHeight = false,\n}: HydrateProps): VNode {\n if (!isValidElement(children)) {\n throw new Error('<Hydrate> expects a single Preact component as its child');\n }\n\n if (isFragment(children)) {\n throw new Error('<Hydrate> does not support fragments');\n }\n\n const content = children as VNode;\n const { type: Component, props } = content;\n if (typeof Component !== 'function') {\n throw new Error('<Hydrate> expects a Preact component');\n }\n\n // Note that we don't use `Component.name` here because it can be minified or mangled.\n const componentName = nameOverride ?? Component.displayName;\n if (!componentName) {\n // This is only defined in development, not in production when the function name is minified.\n const componentDevName = Component.name || 'UnknownComponent';\n throw new AugmentedError(\n '<Hydrate> expects a component to have a displayName or nameOverride.',\n {\n info: html`\n <div>\n <p>Make sure to add a displayName to the component:</p>\n <pre><code>export const ${componentDevName} = ...;\n// Add this line:\n${componentDevName}.displayName = '${componentDevName}';</code></pre>\n </div>\n `,\n },\n );\n }\n\n const scriptPath = `esm-bundles/hydrated-components/${componentName}.ts`;\n let compiledScriptSrc = '';\n try {\n compiledScriptSrc = compiledScriptPath(scriptPath);\n } catch (error) {\n throw new AugmentedError(`Could not find script for component \"${componentName}\".`, {\n info: html`\n <div>\n Make sure you create a script at\n <code>esm-bundles/hydrated-components/${componentName}.ts</code> registering the\n component:\n <pre><code>import { registerHydratedComponent } from '@prairielearn/preact/hydrated-component';\n\nimport { ${componentName} } from './path/to/component.js';\n\nregisterHydratedComponent(${componentName});</code></pre>\n </div>\n `,\n cause: error,\n });\n }\n const scriptPreloads = compiledScriptPreloadPaths(scriptPath);\n return (\n <Fragment>\n <script type=\"module\" src={compiledScriptSrc} />\n {scriptPreloads.map((preloadPath) => (\n <link key={preloadPath} rel=\"modulepreload\" href={preloadPath} />\n ))}\n <script\n type=\"application/json\"\n // eslint-disable-next-line @eslint-react/dom/no-dangerously-set-innerhtml\n dangerouslySetInnerHTML={{\n __html: escapeJsonForHtml(props),\n }}\n data-component={componentName}\n data-component-props\n />\n <div\n data-component={componentName}\n className={clsx('js-hydrated-component', { 'h-100': fullHeight }, className)}\n >\n <Component {...props} />\n </div>\n </Fragment>\n );\n}\n\n/**\n * Renders a Preact component for client-side hydration and returns an HTML-safe string.\n * This function is intended to be used within a tagged template literal, e.g. html`...`.\n *\n * @param content - A Preact VNode to render to HTML.\n * @returns An `HtmlSafeString` containing the rendered HTML.\n */\nexport function hydrateHtml<T>(\n content: VNode<T>,\n props: Omit<HydrateProps, 'children'> = {},\n): HtmlSafeString {\n // Useful for adding Preact components to existing tagged-template pages.\n return renderHtml(<Hydrate {...props}>{content}</Hydrate>);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prairielearn/preact",
3
- "version": "1.0.7",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,7 +21,7 @@
21
21
  "@prairielearn/compiled-assets": "^3.3.1",
22
22
  "@prairielearn/error": "^2.0.23",
23
23
  "@prairielearn/html": "^4.0.23",
24
- "@prairielearn/preact-cjs": "^1.1.7",
24
+ "@prairielearn/preact-cjs": "^2.0.0",
25
25
  "@prairielearn/utils": "^2.0.4",
26
26
  "clsx": "^2.1.1",
27
27
  "preact": "^10.28.1",
package/src/server.tsx CHANGED
@@ -47,7 +47,7 @@ interface HydrateProps {
47
47
  /** Whether to apply full height styles. */
48
48
  fullHeight?: boolean;
49
49
  /** Optional CSS class to apply to the container. */
50
- class?: string;
50
+ className?: string;
51
51
  }
52
52
 
53
53
  /**
@@ -59,7 +59,7 @@ interface HydrateProps {
59
59
  export function Hydrate({
60
60
  children,
61
61
  nameOverride,
62
- class: className,
62
+ className,
63
63
  fullHeight = false,
64
64
  }: HydrateProps): VNode {
65
65
  if (!isValidElement(children)) {
@@ -135,7 +135,7 @@ registerHydratedComponent(${componentName});</code></pre>
135
135
  />
136
136
  <div
137
137
  data-component={componentName}
138
- class={clsx('js-hydrated-component', { 'h-100': fullHeight }, className)}
138
+ className={clsx('js-hydrated-component', { 'h-100': fullHeight }, className)}
139
139
  >
140
140
  <Component {...props} />
141
141
  </div>