diff --git a/packages/duoyun-ui/docs/en/01-guide/06-integrate.md b/packages/duoyun-ui/docs/en/01-guide/06-integrate.md
new file mode 100644
index 00000000..a79afbb9
--- /dev/null
+++ b/packages/duoyun-ui/docs/en/01-guide/06-integrate.md
@@ -0,0 +1,64 @@
+# Used in projects like React
+
+[Most front-end frameworks/libraries](https://custom-elements-everywhere.com/) can seamlessly use custom elements, such as assigning Attribute/Property, registering events,
+However, there is no [type hint](https://code.visualstudio.com/docs/editor/intellisense) when using custom elements directly, and ESLint is not supported, so DuoyunUI re-exported it and perfectly adapted to React/Vue/Svelte.
+
+## React
+
+> [!NOTE]
+> Only the experimental version of React supports custom elements. Use `npm install react@experimental react-dom@experimental` to install the experimental version of React.
+
+Use DuoyunUI like any other React component library:
+
+
+
+## Vue
+
+DuoyunUI also exports Vue components, which are used the same as React. The only difference is that the path is changed from `react` to `vue`,
+in addition, custom elements need to be specified in the Vue configuration file:
+
+```js
+{
+ compilerOptions: {
+ isCustomElement: (tag) => tag.startsWith('dy-');
+ }
+}
+```
+
+In the Vue project, it also supports directly writing custom elements, but [distinguish](../02-elements/card#api) is it Attribute or Property:
+
+
+
+## Svelte
+
+DuoyunUI does not re-export as a Svelte component, and you can use the custom element directly:
+
+
+
+> [!NOTE]
+> Use the `Sveltekit`, please make sure the `Svelte` is installed as a `dependencies` instead of `DevDependenCies`, otherwise the type cannot be import successfully;
+> if you compile the error of "Unexpected token 'export'", please add the following code to `vite.config.ts`:
+>
+> ```js
+> {
+> ssr: {
+> noExternal: ['@mantou/gem', 'duoyun-ui'];
+> }
+> }
+> ```
+
+## SSR
+
+DuoyunUI does not support SSR, to be precise, Next/Nuxt/Svelte do not support SSR for custom elements. The ShadowDOM of custom elements is generated at runtime. To ensure proper server-side rendering, import `@mantou/gem/helper/ssr-shim` at the entry point of your front-end code.
+
+- Next.js: `pages/_app.tsx`
+- Nuxt.js: `app.config.ts`
+- SvelteKit: `src/hooks.server.ts`
+
+If you want to use DuoyunUI routing, you can use ``. To avoid layout issues on the initial page load, add the following global styles:
+
+```css
+:not(:defined) {
+ display: none;
+}
+```
diff --git a/packages/duoyun-ui/docs/zh/01-guide/06-integrate.md b/packages/duoyun-ui/docs/zh/01-guide/06-integrate.md
new file mode 100644
index 00000000..cc8b072b
--- /dev/null
+++ b/packages/duoyun-ui/docs/zh/01-guide/06-integrate.md
@@ -0,0 +1,65 @@
+# 在 React 等项目中使用
+
+[大多数前端框架/库](https://custom-elements-everywhere.com/)都能无缝使用自定义元素,比如分配 Attribute/Property,注册事件,
+但是直接使用自定义元素没有[类型提示](https://code.visualstudio.com/docs/editor/intellisense)、不支持 ESLint,所以 DuoyunUI 进行了重导出,完美适配了 React/Vue/Svelte。
+
+## React
+
+> [!NOTE]
+> React 的实验版才支持自定义元素,使用 `npm install react@experimental react-dom@experimental` 安装 React 实验版。
+
+跟使用其他 React 组件库一样使用 DuoyunUI:
+
+
+
+## Vue
+
+DuoyunUI 也导出了 Vue 组件,使用和 React 一样,唯一的区别是路径将 `react` 改成 `vue`,
+另外需要在 Vue 配置文件中指定自定义元素:
+
+```js
+{
+ compilerOptions: {
+ isCustomElement: (tag) => tag.startsWith('dy-');
+ }
+}
+```
+
+在 Vue 中也支持直接写自定义元素,但是要[区分](../02-elements/card#api)是 Attribute 还是 Property:
+
+
+
+## Svelte
+
+DuoyunUI 没有重导出为 Svelte 组件,直接使用自定义元素即可:
+
+
+
+> [!NOTE]
+> 使用 `SvelteKit` 请确保 `svelte` 安装成 `dependencies` 而非 `devDependencies`,否则类型不能成功导入;
+> 如果编译出现“Unexpected token 'export'”的错误请在 `vite.config.ts` 中添加下面代码:
+>
+> ```js
+> {
+> ssr: {
+> noExternal: ['@mantou/gem', 'duoyun-ui'];
+> }
+> }
+> ```
+
+## SSR
+
+DuoyunUI 不支持 SSR,确切的说是 Next/Nuxt/Svelte 不支持自定义元素 SSR,自定义元素的 ShadowDOM 是运行时生成,
+为了能在服务端正确运行,在前端代码的入口位置导入 `@mantou/gem/helper/ssr-shim`:
+
+- Next.js: `pages/_app.tsx`
+- Nuxt.js: `app.config.ts`
+- SvelteKit: `src/hooks.server.ts`
+
+如果要使用 DuoyunUI 的路由,可以使用 ``。为了避免首屏排版错误添加下面全局样式:
+
+```css
+:not(:defined) {
+ display: none;
+}
+```
diff --git a/packages/duoyun-ui/gem-book.cli.json b/packages/duoyun-ui/gem-book.cli.json
index 769c38b4..3d32ae22 100644
--- a/packages/duoyun-ui/gem-book.cli.json
+++ b/packages/duoyun-ui/gem-book.cli.json
@@ -3,6 +3,6 @@
"icon": "../../logo.png",
"i18n": true,
"sourceBranch": "docs",
- "plugin": ["media", "api", "sandpack"],
+ "plugin": ["media", "api", "sandpack", "raw"],
"debug": true
}
diff --git a/packages/duoyun-ui/package.json b/packages/duoyun-ui/package.json
index 401b42a7..052bee47 100644
--- a/packages/duoyun-ui/package.json
+++ b/packages/duoyun-ui/package.json
@@ -1,6 +1,6 @@
{
"name": "duoyun-ui",
- "version": "1.0.1",
+ "version": "1.0.2",
"description": "WebComponts UI",
"exports": {
"./elements/*": "./elements/*.js",
diff --git a/packages/duoyun-ui/src/elements/space.ts b/packages/duoyun-ui/src/elements/space.ts
index 3ef68d79..3c700920 100644
--- a/packages/duoyun-ui/src/elements/space.ts
+++ b/packages/duoyun-ui/src/elements/space.ts
@@ -24,7 +24,7 @@ const style = createCSSSheet(css`
@customElement('dy-space')
@adoptedStyle(style)
export class DuoyunSpaceElement extends GemElement {
- @attribute size: 'nomal' | 'small' | 'large';
+ @attribute size: 'normal' | 'small' | 'large';
constructor() {
super({ isLight: true });
diff --git a/packages/gem-book/src/element/elements/main.ts b/packages/gem-book/src/element/elements/main.ts
index 22a253bd..9fad2768 100644
--- a/packages/gem-book/src/element/elements/main.ts
+++ b/packages/gem-book/src/element/elements/main.ts
@@ -191,15 +191,7 @@ const style = createCSSSheet(css`
opacity: 1;
}
code {
- font-family:
- Source Code Pro,
- ui-monospace,
- SFMono-Regular,
- Menlo,
- Monaco,
- Consolas,
- Liberation Mono,
- Courier New,
+ font-family: Source Code Pro, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New,
monospace;
padding: 0.15em 0.4em 0.1em;
font-size: 0.9em;
@@ -244,6 +236,9 @@ export class Main extends GemElement {
.link:hover {
border-color: currentColor;
}
+ .link svg:last-child {
+ margin-inline-end: 0.2em;
+ }
`;
#hashChangeHandle = () => {
diff --git a/packages/gem-book/src/element/elements/pre.ts b/packages/gem-book/src/element/elements/pre.ts
index 70b35efb..76436935 100644
--- a/packages/gem-book/src/element/elements/pre.ts
+++ b/packages/gem-book/src/element/elements/pre.ts
@@ -381,7 +381,7 @@ export class Pre extends GemElement {
? this.#getRanges(this.range).map(([start, end]) => {
let result = '';
for (let i = start - 1; i < (end || lines.length); i++) {
- result += lines[i] + '\n';
+ result += (lines[i] || '') + '\n';
}
return result;
})
diff --git a/packages/gem-book/src/plugins/raw.ts b/packages/gem-book/src/plugins/raw.ts
index b6d44913..600183eb 100644
--- a/packages/gem-book/src/plugins/raw.ts
+++ b/packages/gem-book/src/plugins/raw.ts
@@ -1,26 +1,63 @@
import type { GemBookElement } from '../element';
+type State = {
+ content: string;
+ error?: any;
+};
+
customElements.whenDefined('gem-book').then(() => {
const { GemBookPluginElement } = customElements.get('gem-book') as typeof GemBookElement;
- const { config, Gem, devMode } = GemBookPluginElement;
- const { attribute, customElement } = Gem;
+ const { config, Gem, devMode, theme } = GemBookPluginElement;
+ const { attribute, customElement, html, createCSSSheet, css, adoptedStyle } = Gem;
+
+ const style = createCSSSheet(css`
+ :host {
+ display: contents;
+ }
+ .loading,
+ .error {
+ display: flex;
+ place-items: center;
+ place-content: center;
+ min-height: 20em;
+ background: rgba(${theme.textColorRGB}, 0.05);
+ }
+ .loading {
+ opacity: 0.5;
+ }
+ .error {
+ color: ${theme.cautionColor};
+ }
+ @keyframes display {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+ }
+ gem-book-pre {
+ animation: display 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
+ }
+ `);
@customElement('gbp-raw')
- class _GbpRawElement extends GemBookPluginElement {
+ @adoptedStyle(style)
+ class _GbpRawElement extends GemBookPluginElement {
@attribute src: string;
@attribute codelang: string;
@attribute range: string;
@attribute highlight: string;
- mounted() {
- this.#renderContent();
- }
+ state: State = {
+ content: '',
+ };
- updated() {
- this.#renderContent();
+ get #codelang() {
+ return this.codelang || this.src.split('.').pop() || '';
}
- #getRemoteUrl() {
+ get #url() {
if (!this.src) return '';
let url = this.src;
@@ -35,19 +72,33 @@ customElements.whenDefined('gem-book').then(() => {
return url;
}
- async #renderContent() {
- if (!this.src) return;
- this.innerHTML = 'Loading...';
+ mounted() {
+ this.effect(
+ async () => {
+ if (!this.src) return;
+ this.setState({ error: false });
+ try {
+ const resp = await fetch(this.#url);
+ if (resp.status === 404) throw new Error(resp.statusText || 'Not Found');
+ this.setState({ content: await resp.text() });
+ } catch (error) {
+ this.setState({ error });
+ }
+ },
+ () => [this.src],
+ );
+ }
- const text = await (await fetch(this.#getRemoteUrl())).text();
- const div = document.createElement('div');
- div.textContent = text;
- const content = div.innerHTML;
+ render() {
+ const { content, error } = this.state;
+ if (error) return html`${error}
`;
+ if (!content) return html`Loading...
`;
- const extension = this.src.split('.').pop() || '';
- this.innerHTML = `${content}`;
+ return html`
+ ${content}
+ `;
}
}
});
diff --git a/packages/gem-devtools/src/elements/section.ts b/packages/gem-devtools/src/elements/section.ts
index 178f826d..d9575482 100644
--- a/packages/gem-devtools/src/elements/section.ts
+++ b/packages/gem-devtools/src/elements/section.ts
@@ -15,7 +15,7 @@ import { Item, Path, BuildIn, panelStore } from '../store';
import { theme } from '../theme';
import { inspectValue } from '../scripts/inspect-value';
import { execution } from '../common';
-import { setValue } from '../scripts/set-value';
+import { setGemPropValue } from '../scripts/set-value';
const maybeBuildInPrefix = '[[Gem?]] ';
const buildInPrefix = '[[Gem]] ';
@@ -214,13 +214,16 @@ export class Section extends GemElement {
? [kebabToCamelCase(item.name)]
: [item.name];
const onInput = (evt: Event) => {
- execution(setValue, [path, (evt.target as HTMLInputElement).value]);
+ execution(setGemPropValue, [path, (evt.target as HTMLInputElement).value]);
};
const onInputNumber = (evt: Event) => {
- execution(setValue, [path, Number((evt.target as HTMLInputElement).value)]);
+ execution(setGemPropValue, [path, Number((evt.target as HTMLInputElement).value) || 0]);
};
const toggleValue = (evt: MouseEvent) => {
- execution(setValue, [path, (evt.target as HTMLInputElement).textContent?.trim() === 'true' ? false : true]);
+ execution(setGemPropValue, [
+ path,
+ (evt.target as HTMLInputElement).textContent?.trim() === 'true' ? false : true,
+ ]);
};
switch (item.type) {
case 'string':
diff --git a/packages/gem-devtools/src/scripts/set-value.ts b/packages/gem-devtools/src/scripts/set-value.ts
index db3e8529..e2d48f9e 100644
--- a/packages/gem-devtools/src/scripts/set-value.ts
+++ b/packages/gem-devtools/src/scripts/set-value.ts
@@ -1,9 +1,13 @@
import { Path } from '../store';
-export const setValue = (path: Path, value: string | number | boolean | null) => {
+declare let $0: any;
+
+export const setGemPropValue = (path: Path, value: string | number | boolean | null) => {
const key = String(path.pop());
const obj = window.__GEM_DEVTOOLS__PRELOAD__.readProp(path);
obj[key] = value;
+
+ $0.update();
};
diff --git a/packages/gem-examples/package.json b/packages/gem-examples/package.json
index 8afebda2..a351dbd4 100644
--- a/packages/gem-examples/package.json
+++ b/packages/gem-examples/package.json
@@ -9,7 +9,7 @@
},
"dependencies": {
"@mantou/gem": "^1.7.1",
- "duoyun-ui": "^1.0.1"
+ "duoyun-ui": "^1.0.2"
},
"devDependencies": {
"@gemjs/config": "^1.6.11",