@slidejs/runner-splide 0.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.
@@ -0,0 +1,25 @@
1
+
2
+ > @slidejs/runner-splide@0.1.0 build /Volumes/ORICO/ws/prj/slidejs/slidejs/packages/@slidejs/runner-splide
3
+ > vite build
4
+
5
+ vite v5.4.21 building for production...
6
+ src/adapter.ts:320:10 - error TS2352: Conversion of type 'HTMLElement' to type 'Record<string, unknown>' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
7
+ Index signature for type 'string' is missing in type 'HTMLElement'.
8
+
9
+ 320 (element as Record<string, unknown>)[key] = value;
10
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11
+
12
+ transforming...
13
+ ✓ 3 modules transformed.
14
+ rendering chunks...
15
+
16
+ [vite:dts] Start generate declaration files...
17
+ computing gzip size...
18
+ dist/index.js 7.27 kB │ gzip: 2.40 kB │ map: 19.27 kB
19
+ [vite:dts] Start rollup declaration files...
20
+ Analysis will use the bundled TypeScript version 5.4.2
21
+ *** The target project appears to use TypeScript 5.9.3 which is newer than the bundled compiler engine; consider upgrading API Extractor.
22
+ [vite:dts] Declaration files built in 3190ms.
23
+
24
+ dist/index.cjs 4.65 kB │ gzip: 1.62 kB │ map: 18.34 kB
25
+ ✓ built in 3.38s
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=require("@splidejs/splide"),a=require("@slidejs/dsl"),h=require("@slidejs/runner");class c{constructor(){this.name="splide",this.eventHandlers=new Map}async initialize(i,t){try{if(this.createSplideStructure(i),!this.splideContainer)throw new Error("Splide container not created");const e={type:"slide",perPage:1,perMove:1,gap:"1rem",pagination:!0,arrows:!0,keyboard:"global",...t==null?void 0:t.splideConfig};this.splide=new p.Splide(this.splideContainer,e),this.splide.mount(),await new Promise(n=>{requestAnimationFrame(()=>{n()})}),this.setupEventListeners(),this.emit("ready")}catch(e){const n=e instanceof Error?e.message:String(e);throw this.emit("error",{message:n}),new Error(`Failed to initialize SplideAdapter: ${n}`)}}async render(i){if(!this.splideList||!this.splide)throw new Error("SplideAdapter not initialized");try{this.splideList.innerHTML="";for(const t of i){const e=await this.renderSlide(t);this.splideList.appendChild(e)}this.splide.refresh(),this.emit("slideRendered",{totalSlides:i.length})}catch(t){const e=t instanceof Error?t.message:String(t);throw this.emit("error",{message:e}),new Error(`Failed to render slides: ${e}`)}}async destroy(){this.splide&&(this.splide.destroy(),this.splide=void 0),this.splideContainer&&(this.splideContainer.innerHTML="",this.splideContainer=void 0),this.splideTrack=void 0,this.splideList=void 0,this.eventHandlers.clear()}navigateTo(i){if(!this.splide)throw new Error("SplideAdapter not initialized");this.splide.go(i)}getCurrentIndex(){return this.splide?this.splide.index:0}getTotalSlides(){return this.splide?this.splide.length:0}async updateSlide(i,t){if(!this.splideList||!this.splide)throw new Error("SplideAdapter not initialized");try{const n=this.splideList.querySelectorAll(".splide__slide")[i];if(!n)throw new Error(`Slide at index ${i} not found`);const s=await this.renderSlide(t);n.replaceWith(s),this.splide.refresh()}catch(e){const n=e instanceof Error?e.message:String(e);throw this.emit("error",{message:n}),new Error(`Failed to update slide: ${n}`)}}on(i,t){this.eventHandlers.has(i)||this.eventHandlers.set(i,new Set),this.eventHandlers.get(i).add(t)}off(i,t){const e=this.eventHandlers.get(i);e&&e.delete(t)}createSplideStructure(i){const t=document.createElement("div");t.className="splide";const e=document.createElement("div");e.className="splide__track";const n=document.createElement("ul");n.className="splide__list",e.appendChild(n),t.appendChild(e),i.appendChild(t),this.splideContainer=t,this.splideTrack=e,this.splideList=n}setupEventListeners(){this.splide&&this.splide.on("moved",(i,t)=>{this.emit("slideChanged",{index:i,previousIndex:t,from:t,to:i})})}async renderSlide(i){const t=document.createElement("li");if(t.className="splide__slide",i.content.type==="dynamic"){const e=await this.renderDynamicContent(i.content.component,i.content.props);t.appendChild(e)}else{const e=this.renderTextContent(i.content.lines);t.appendChild(e)}return t}async renderDynamicContent(i,t){const e=document.createElement(i);for(const[n,s]of Object.entries(t))typeof s=="string"||typeof s=="number"?e.setAttribute(n,String(s)):typeof s=="boolean"?s&&e.setAttribute(n,""):e[n]=s;return e}renderTextContent(i){const t=document.createElement("div");t.className="slide-content";let e=null;for(const n of i){const s=n.trim();if(!s){e=null;continue}if(s.startsWith("# ")){e=null;const r=document.createElement("h1");r.textContent=s.substring(2),t.appendChild(r);continue}if(s.startsWith("## ")){e=null;const r=document.createElement("h2");r.textContent=s.substring(3),t.appendChild(r);continue}if(s.startsWith("### ")){e=null;const r=document.createElement("h3");r.textContent=s.substring(4),t.appendChild(r);continue}const l=s.match(/^!\[(.*?)\]\((.*?)\)$/);if(l){e=null;const r=document.createElement("img");r.alt=l[1],r.src=l[2],r.style.maxWidth="80%",r.style.maxHeight="500px",t.appendChild(r);continue}if(s.startsWith("- ")){e||(e=document.createElement("ul"),t.appendChild(e));const r=document.createElement("li");r.textContent=s.substring(2),e.appendChild(r);continue}e=null;const o=document.createElement("p");o.textContent=s,t.appendChild(o)}return t}emit(i,t){const e=this.eventHandlers.get(i);if(e)for(const n of e)try{n(t)}catch(s){console.error(`Error in ${i} handler:`,s)}}}async function u(d,i,t){const e=await a.parseSlideDSL(d),n=a.compile(e),s=new c,l=new h.SlideRunner({container:t.container,adapter:s,adapterOptions:{splideConfig:t.splideOptions}});return await l.run(n,i),l}exports.SplideAdapter=c;exports.createSlideRunner=u;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/adapter.ts","../src/runner.ts"],"sourcesContent":["/**\n * @slidejs/runner-splide - SplideAdapter 适配器实现\n *\n * 将 Slide DSL 渲染为 Splide 幻灯片\n */\n\nimport { Splide } from '@splidejs/splide';\nimport type { Options } from '@splidejs/splide';\nimport type { SlideDefinition } from '@slidejs/core';\nimport type { SlideAdapter, AdapterEvent, EventHandler } from '@slidejs/runner';\nimport type { SplideAdapterOptions } from './types';\n\n/**\n * Splide 适配器\n *\n * 实现 SlideAdapter 接口,将 SlideDefinition 渲染为 Splide 幻灯片\n */\nexport class SplideAdapter implements SlideAdapter {\n readonly name = 'splide';\n\n private splide?: Splide;\n private splideContainer?: HTMLElement;\n private splideTrack?: HTMLElement;\n private splideList?: HTMLElement;\n private eventHandlers: Map<AdapterEvent, Set<EventHandler>> = new Map();\n\n /**\n * 初始化 Splide 适配器\n *\n * @param container - 容器元素\n * @param options - Splide 选项\n */\n async initialize(container: HTMLElement, options?: SplideAdapterOptions): Promise<void> {\n try {\n // 创建 Splide DOM 结构\n this.createSplideStructure(container);\n\n // 初始化 Splide\n if (!this.splideContainer) {\n throw new Error('Splide container not created');\n }\n\n const splideConfig: Options = {\n // 默认配置\n type: 'slide',\n perPage: 1,\n perMove: 1,\n gap: '1rem',\n pagination: true,\n arrows: true,\n keyboard: 'global',\n ...options?.splideConfig,\n };\n\n this.splide = new Splide(this.splideContainer, splideConfig);\n this.splide.mount();\n\n // 等待初始化完成\n await new Promise<void>(resolve => {\n requestAnimationFrame(() => {\n resolve();\n });\n });\n\n // 设置事件监听\n this.setupEventListeners();\n\n // 触发 ready 事件\n this.emit('ready');\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.emit('error', { message: errorMessage });\n throw new Error(`Failed to initialize SplideAdapter: ${errorMessage}`);\n }\n }\n\n /**\n * 渲染幻灯片\n *\n * @param slides - 幻灯片定义数组\n */\n async render(slides: SlideDefinition[]): Promise<void> {\n if (!this.splideList || !this.splide) {\n throw new Error('SplideAdapter not initialized');\n }\n\n try {\n // 清空现有幻灯片\n this.splideList.innerHTML = '';\n\n // 渲染每张幻灯片\n for (const slide of slides) {\n const slideElement = await this.renderSlide(slide);\n this.splideList.appendChild(slideElement);\n }\n\n // 刷新 Splide(重新计算和更新)\n this.splide.refresh();\n\n // 触发 slideRendered 事件\n this.emit('slideRendered', { totalSlides: slides.length });\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.emit('error', { message: errorMessage });\n throw new Error(`Failed to render slides: ${errorMessage}`);\n }\n }\n\n /**\n * 销毁适配器\n */\n async destroy(): Promise<void> {\n if (this.splide) {\n this.splide.destroy();\n this.splide = undefined;\n }\n\n if (this.splideContainer) {\n this.splideContainer.innerHTML = '';\n this.splideContainer = undefined;\n }\n\n this.splideTrack = undefined;\n this.splideList = undefined;\n this.eventHandlers.clear();\n }\n\n /**\n * 导航到指定幻灯片\n *\n * @param index - 幻灯片索引\n */\n navigateTo(index: number): void {\n if (!this.splide) {\n throw new Error('SplideAdapter not initialized');\n }\n\n this.splide.go(index);\n }\n\n /**\n * 获取当前幻灯片索引\n */\n getCurrentIndex(): number {\n if (!this.splide) {\n return 0;\n }\n\n return this.splide.index;\n }\n\n /**\n * 获取幻灯片总数\n */\n getTotalSlides(): number {\n if (!this.splide) {\n return 0;\n }\n\n return this.splide.length;\n }\n\n /**\n * 更新指定幻灯片\n *\n * @param index - 幻灯片索引\n * @param slide - 新的幻灯片定义\n */\n async updateSlide(index: number, slide: SlideDefinition): Promise<void> {\n if (!this.splideList || !this.splide) {\n throw new Error('SplideAdapter not initialized');\n }\n\n try {\n // 获取指定索引的 slide 元素\n const slides = this.splideList.querySelectorAll('.splide__slide');\n const targetSlide = slides[index] as HTMLElement;\n\n if (!targetSlide) {\n throw new Error(`Slide at index ${index} not found`);\n }\n\n // 渲染新的幻灯片内容\n const newSlide = await this.renderSlide(slide);\n\n // 替换旧的 slide\n targetSlide.replaceWith(newSlide);\n\n // 刷新 Splide\n this.splide.refresh();\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.emit('error', { message: errorMessage });\n throw new Error(`Failed to update slide: ${errorMessage}`);\n }\n }\n\n /**\n * 注册事件监听器\n *\n * @param event - 事件类型\n * @param handler - 事件处理器\n */\n on(event: AdapterEvent, handler: EventHandler): void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler);\n }\n\n /**\n * 移除事件监听器\n *\n * @param event - 事件类型\n * @param handler - 事件处理器\n */\n off(event: AdapterEvent, handler: EventHandler): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.delete(handler);\n }\n }\n\n /**\n * 创建 Splide DOM 结构\n *\n * @param container - 容器元素\n */\n private createSplideStructure(container: HTMLElement): void {\n // 创建 .splide 容器\n const splideDiv = document.createElement('div');\n splideDiv.className = 'splide';\n\n // 创建 .splide__track 容器\n const trackDiv = document.createElement('div');\n trackDiv.className = 'splide__track';\n\n // 创建 .splide__list 容器\n const listUl = document.createElement('ul');\n listUl.className = 'splide__list';\n\n trackDiv.appendChild(listUl);\n splideDiv.appendChild(trackDiv);\n container.appendChild(splideDiv);\n\n this.splideContainer = splideDiv;\n this.splideTrack = trackDiv;\n this.splideList = listUl;\n }\n\n /**\n * 设置 Splide 事件监听\n */\n private setupEventListeners(): void {\n if (!this.splide) {\n return;\n }\n\n // 监听幻灯片切换事件\n this.splide.on('moved', (newIndex, prevIndex) => {\n this.emit('slideChanged', {\n index: newIndex,\n previousIndex: prevIndex,\n from: prevIndex,\n to: newIndex,\n });\n });\n }\n\n /**\n * 渲染单张幻灯片\n *\n * @param slide - 幻灯片定义\n * @returns slide 元素\n */\n private async renderSlide(slide: SlideDefinition): Promise<HTMLElement> {\n const slideElement = document.createElement('li');\n slideElement.className = 'splide__slide';\n\n // 渲染内容\n if (slide.content.type === 'dynamic') {\n // 动态内容(Web Components)\n const content = await this.renderDynamicContent(slide.content.component, slide.content.props);\n slideElement.appendChild(content);\n } else {\n // 静态文本内容\n const content = this.renderTextContent(slide.content.lines);\n slideElement.appendChild(content);\n }\n\n return slideElement;\n }\n\n /**\n * 渲染动态内容(Web Component)\n *\n * @param component - 组件名称\n * @param props - 组件属性\n * @returns 组件元素\n */\n private async renderDynamicContent(\n component: string,\n props: Record<string, unknown>\n ): Promise<HTMLElement> {\n // 创建 Web Component 元素\n const element = document.createElement(component);\n\n // 设置属性\n for (const [key, value] of Object.entries(props)) {\n if (typeof value === 'string' || typeof value === 'number') {\n // 字符串和数字 → HTML attributes\n element.setAttribute(key, String(value));\n } else if (typeof value === 'boolean') {\n // 布尔值 → HTML attributes(true 时设置空属性)\n if (value) {\n element.setAttribute(key, '');\n }\n } else {\n // 对象和数组 → JavaScript properties\n (element as Record<string, unknown>)[key] = value;\n }\n }\n\n return element;\n }\n\n /**\n * 渲染文本内容\n *\n * 支持以下格式:\n * - # 标题 -> h1\n * - ## 标题 -> h2\n * - ### 标题 -> h3\n * - ![alt](url) -> img\n * - - 列表项 -> ul/li\n * - 普通文本 -> p\n *\n * @param lines - 文本行数组\n * @returns 内容容器元素\n */\n private renderTextContent(lines: string[]): HTMLElement {\n const container = document.createElement('div');\n container.className = 'slide-content';\n\n let listContainer: HTMLUListElement | null = null;\n\n for (const line of lines) {\n const trimmedLine = line.trim();\n\n // 空行,结束列表\n if (!trimmedLine) {\n listContainer = null;\n continue;\n }\n\n // 标题 - # H1\n if (trimmedLine.startsWith('# ')) {\n listContainer = null;\n const h1 = document.createElement('h1');\n h1.textContent = trimmedLine.substring(2);\n container.appendChild(h1);\n continue;\n }\n\n // 标题 - ## H2\n if (trimmedLine.startsWith('## ')) {\n listContainer = null;\n const h2 = document.createElement('h2');\n h2.textContent = trimmedLine.substring(3);\n container.appendChild(h2);\n continue;\n }\n\n // 标题 - ### H3\n if (trimmedLine.startsWith('### ')) {\n listContainer = null;\n const h3 = document.createElement('h3');\n h3.textContent = trimmedLine.substring(4);\n container.appendChild(h3);\n continue;\n }\n\n // 图片 - ![alt](url)\n const imageMatch = trimmedLine.match(/^!\\[(.*?)\\]\\((.*?)\\)$/);\n if (imageMatch) {\n listContainer = null;\n const img = document.createElement('img');\n img.alt = imageMatch[1];\n img.src = imageMatch[2];\n img.style.maxWidth = '80%';\n img.style.maxHeight = '500px';\n container.appendChild(img);\n continue;\n }\n\n // 列表项 - - 项目\n if (trimmedLine.startsWith('- ')) {\n if (!listContainer) {\n listContainer = document.createElement('ul');\n container.appendChild(listContainer);\n }\n const li = document.createElement('li');\n li.textContent = trimmedLine.substring(2);\n listContainer.appendChild(li);\n continue;\n }\n\n // 普通文本\n listContainer = null;\n const p = document.createElement('p');\n p.textContent = trimmedLine;\n container.appendChild(p);\n }\n\n return container;\n }\n\n /**\n * 触发事件\n *\n * @param event - 事件类型\n * @param data - 事件数据\n */\n private emit(event: AdapterEvent, data?: unknown): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n for (const handler of handlers) {\n try {\n handler(data);\n } catch (error) {\n console.error(`Error in ${event} handler:`, error);\n }\n }\n }\n }\n}\n","/**\n * @slidejs/runner-splide - SlideRunner 工厂函数\n *\n * 提供创建配置好的 SlideRunner 实例的便捷方法\n */\n\nimport { parseSlideDSL, compile } from '@slidejs/dsl';\nimport { SlideRunner } from '@slidejs/runner';\nimport type { SlideContext } from '@slidejs/context';\nimport { SplideAdapter } from './adapter';\nimport type { SplideAdapterOptions } from './types';\n\n/**\n * SlideRunner 配置选项\n */\nexport interface SlideRunnerConfig {\n /**\n * 容器选择器或 HTMLElement\n */\n container: string | HTMLElement;\n\n /**\n * Splide 配置选项\n */\n splideOptions?: SplideAdapterOptions['splideConfig'];\n}\n\n/**\n * 从 DSL 源代码创建并运行 SlideRunner\n *\n * @example\n * ```typescript\n * import { createSlideRunner } from '@slidejs/runner-splide';\n *\n * const dslSource = `\n * present quiz \"demo\" {\n * rules {\n * rule start \"intro\" {\n * slide {\n * content text { \"Hello World!\" }\n * }\n * }\n * }\n * }\n * `;\n *\n * const context = { sourceType: 'quiz', sourceId: 'demo', items: [] };\n * const runner = await createSlideRunner(dslSource, context, {\n * container: '#app',\n * splideOptions: {\n * type: 'slide',\n * perPage: 1,\n * pagination: true,\n * arrows: true,\n * },\n * });\n * ```\n */\nexport async function createSlideRunner<TContext extends SlideContext = SlideContext>(\n dslSource: string,\n context: TContext,\n config: SlideRunnerConfig\n): Promise<SlideRunner<TContext>> {\n // 1. 解析 DSL\n const ast = await parseSlideDSL(dslSource);\n\n // 2. 编译为 SlideDSL\n const slideDSL = compile<TContext>(ast);\n\n // 3. 创建适配器和 Runner\n const adapter = new SplideAdapter();\n const runner = new SlideRunner<TContext>({\n container: config.container,\n adapter,\n adapterOptions: {\n splideConfig: config.splideOptions,\n },\n });\n\n // 4. 运行演示(这会初始化适配器并渲染幻灯片)\n await runner.run(slideDSL, context);\n\n // 注意:需要手动调用 runner.play() 来启动演示(导航到第一张幻灯片)\n // 返回 runner 以便用户可以控制演示\n return runner;\n}\n"],"names":["SplideAdapter","container","options","splideConfig","Splide","resolve","error","errorMessage","slides","slide","slideElement","index","targetSlide","newSlide","event","handler","handlers","splideDiv","trackDiv","listUl","newIndex","prevIndex","content","component","props","element","key","value","lines","listContainer","line","trimmedLine","h1","h2","h3","imageMatch","img","li","p","data","createSlideRunner","dslSource","context","config","ast","parseSlideDSL","slideDSL","compile","adapter","runner","SlideRunner"],"mappings":"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"}
@@ -0,0 +1,188 @@
1
+ import { AdapterEvent } from '@slidejs/runner';
2
+ import { AdapterOptions } from '@slidejs/runner';
3
+ import { EventHandler } from '@slidejs/runner';
4
+ import { Options } from '@splidejs/splide';
5
+ import { SlideAdapter } from '@slidejs/runner';
6
+ import { SlideContext } from '@slidejs/context';
7
+ import { SlideDefinition } from '@slidejs/core';
8
+ import { SlideRunner } from '@slidejs/runner';
9
+
10
+ /**
11
+ * 从 DSL 源代码创建并运行 SlideRunner
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { createSlideRunner } from '@slidejs/runner-splide';
16
+ *
17
+ * const dslSource = `
18
+ * present quiz "demo" {
19
+ * rules {
20
+ * rule start "intro" {
21
+ * slide {
22
+ * content text { "Hello World!" }
23
+ * }
24
+ * }
25
+ * }
26
+ * }
27
+ * `;
28
+ *
29
+ * const context = { sourceType: 'quiz', sourceId: 'demo', items: [] };
30
+ * const runner = await createSlideRunner(dslSource, context, {
31
+ * container: '#app',
32
+ * splideOptions: {
33
+ * type: 'slide',
34
+ * perPage: 1,
35
+ * pagination: true,
36
+ * arrows: true,
37
+ * },
38
+ * });
39
+ * ```
40
+ */
41
+ export declare function createSlideRunner<TContext extends SlideContext = SlideContext>(dslSource: string, context: TContext, config: SlideRunnerConfig): Promise<SlideRunner<TContext>>;
42
+
43
+ /**
44
+ * SlideRunner 配置选项
45
+ */
46
+ export declare interface SlideRunnerConfig {
47
+ /**
48
+ * 容器选择器或 HTMLElement
49
+ */
50
+ container: string | HTMLElement;
51
+ /**
52
+ * Splide 配置选项
53
+ */
54
+ splideOptions?: SplideAdapterOptions['splideConfig'];
55
+ }
56
+
57
+ /**
58
+ * Splide 适配器
59
+ *
60
+ * 实现 SlideAdapter 接口,将 SlideDefinition 渲染为 Splide 幻灯片
61
+ */
62
+ export declare class SplideAdapter implements SlideAdapter {
63
+ readonly name = "splide";
64
+ private splide?;
65
+ private splideContainer?;
66
+ private splideTrack?;
67
+ private splideList?;
68
+ private eventHandlers;
69
+ /**
70
+ * 初始化 Splide 适配器
71
+ *
72
+ * @param container - 容器元素
73
+ * @param options - Splide 选项
74
+ */
75
+ initialize(container: HTMLElement, options?: SplideAdapterOptions): Promise<void>;
76
+ /**
77
+ * 渲染幻灯片
78
+ *
79
+ * @param slides - 幻灯片定义数组
80
+ */
81
+ render(slides: SlideDefinition[]): Promise<void>;
82
+ /**
83
+ * 销毁适配器
84
+ */
85
+ destroy(): Promise<void>;
86
+ /**
87
+ * 导航到指定幻灯片
88
+ *
89
+ * @param index - 幻灯片索引
90
+ */
91
+ navigateTo(index: number): void;
92
+ /**
93
+ * 获取当前幻灯片索引
94
+ */
95
+ getCurrentIndex(): number;
96
+ /**
97
+ * 获取幻灯片总数
98
+ */
99
+ getTotalSlides(): number;
100
+ /**
101
+ * 更新指定幻灯片
102
+ *
103
+ * @param index - 幻灯片索引
104
+ * @param slide - 新的幻灯片定义
105
+ */
106
+ updateSlide(index: number, slide: SlideDefinition): Promise<void>;
107
+ /**
108
+ * 注册事件监听器
109
+ *
110
+ * @param event - 事件类型
111
+ * @param handler - 事件处理器
112
+ */
113
+ on(event: AdapterEvent, handler: EventHandler): void;
114
+ /**
115
+ * 移除事件监听器
116
+ *
117
+ * @param event - 事件类型
118
+ * @param handler - 事件处理器
119
+ */
120
+ off(event: AdapterEvent, handler: EventHandler): void;
121
+ /**
122
+ * 创建 Splide DOM 结构
123
+ *
124
+ * @param container - 容器元素
125
+ */
126
+ private createSplideStructure;
127
+ /**
128
+ * 设置 Splide 事件监听
129
+ */
130
+ private setupEventListeners;
131
+ /**
132
+ * 渲染单张幻灯片
133
+ *
134
+ * @param slide - 幻灯片定义
135
+ * @returns slide 元素
136
+ */
137
+ private renderSlide;
138
+ /**
139
+ * 渲染动态内容(Web Component)
140
+ *
141
+ * @param component - 组件名称
142
+ * @param props - 组件属性
143
+ * @returns 组件元素
144
+ */
145
+ private renderDynamicContent;
146
+ /**
147
+ * 渲染文本内容
148
+ *
149
+ * 支持以下格式:
150
+ * - # 标题 -> h1
151
+ * - ## 标题 -> h2
152
+ * - ### 标题 -> h3
153
+ * - ![alt](url) -> img
154
+ * - - 列表项 -> ul/li
155
+ * - 普通文本 -> p
156
+ *
157
+ * @param lines - 文本行数组
158
+ * @returns 内容容器元素
159
+ */
160
+ private renderTextContent;
161
+ /**
162
+ * 触发事件
163
+ *
164
+ * @param event - 事件类型
165
+ * @param data - 事件数据
166
+ */
167
+ private emit;
168
+ }
169
+
170
+ /**
171
+ * SplideAdapter 选项
172
+ *
173
+ * Splide CSS 需要手动导入:
174
+ * ```typescript
175
+ * import '@splidejs/splide/css';
176
+ * ```
177
+ *
178
+ * 注意:只需要导入基础 CSS,主题是可选的。
179
+ */
180
+ export declare interface SplideAdapterOptions extends AdapterOptions {
181
+ /**
182
+ * Splide 配置选项
183
+ * @see https://splidejs.com/options/
184
+ */
185
+ splideConfig?: Options;
186
+ }
187
+
188
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,274 @@
1
+ import { Splide as a } from "@splidejs/splide";
2
+ import { parseSlideDSL as c, compile as p } from "@slidejs/dsl";
3
+ import { SlideRunner as h } from "@slidejs/runner";
4
+ class m {
5
+ constructor() {
6
+ this.name = "splide", this.eventHandlers = /* @__PURE__ */ new Map();
7
+ }
8
+ /**
9
+ * 初始化 Splide 适配器
10
+ *
11
+ * @param container - 容器元素
12
+ * @param options - Splide 选项
13
+ */
14
+ async initialize(i, t) {
15
+ try {
16
+ if (this.createSplideStructure(i), !this.splideContainer)
17
+ throw new Error("Splide container not created");
18
+ const e = {
19
+ // 默认配置
20
+ type: "slide",
21
+ perPage: 1,
22
+ perMove: 1,
23
+ gap: "1rem",
24
+ pagination: !0,
25
+ arrows: !0,
26
+ keyboard: "global",
27
+ ...t == null ? void 0 : t.splideConfig
28
+ };
29
+ this.splide = new a(this.splideContainer, e), this.splide.mount(), await new Promise((n) => {
30
+ requestAnimationFrame(() => {
31
+ n();
32
+ });
33
+ }), this.setupEventListeners(), this.emit("ready");
34
+ } catch (e) {
35
+ const n = e instanceof Error ? e.message : String(e);
36
+ throw this.emit("error", { message: n }), new Error(`Failed to initialize SplideAdapter: ${n}`);
37
+ }
38
+ }
39
+ /**
40
+ * 渲染幻灯片
41
+ *
42
+ * @param slides - 幻灯片定义数组
43
+ */
44
+ async render(i) {
45
+ if (!this.splideList || !this.splide)
46
+ throw new Error("SplideAdapter not initialized");
47
+ try {
48
+ this.splideList.innerHTML = "";
49
+ for (const t of i) {
50
+ const e = await this.renderSlide(t);
51
+ this.splideList.appendChild(e);
52
+ }
53
+ this.splide.refresh(), this.emit("slideRendered", { totalSlides: i.length });
54
+ } catch (t) {
55
+ const e = t instanceof Error ? t.message : String(t);
56
+ throw this.emit("error", { message: e }), new Error(`Failed to render slides: ${e}`);
57
+ }
58
+ }
59
+ /**
60
+ * 销毁适配器
61
+ */
62
+ async destroy() {
63
+ this.splide && (this.splide.destroy(), this.splide = void 0), this.splideContainer && (this.splideContainer.innerHTML = "", this.splideContainer = void 0), this.splideTrack = void 0, this.splideList = void 0, this.eventHandlers.clear();
64
+ }
65
+ /**
66
+ * 导航到指定幻灯片
67
+ *
68
+ * @param index - 幻灯片索引
69
+ */
70
+ navigateTo(i) {
71
+ if (!this.splide)
72
+ throw new Error("SplideAdapter not initialized");
73
+ this.splide.go(i);
74
+ }
75
+ /**
76
+ * 获取当前幻灯片索引
77
+ */
78
+ getCurrentIndex() {
79
+ return this.splide ? this.splide.index : 0;
80
+ }
81
+ /**
82
+ * 获取幻灯片总数
83
+ */
84
+ getTotalSlides() {
85
+ return this.splide ? this.splide.length : 0;
86
+ }
87
+ /**
88
+ * 更新指定幻灯片
89
+ *
90
+ * @param index - 幻灯片索引
91
+ * @param slide - 新的幻灯片定义
92
+ */
93
+ async updateSlide(i, t) {
94
+ if (!this.splideList || !this.splide)
95
+ throw new Error("SplideAdapter not initialized");
96
+ try {
97
+ const n = this.splideList.querySelectorAll(".splide__slide")[i];
98
+ if (!n)
99
+ throw new Error(`Slide at index ${i} not found`);
100
+ const s = await this.renderSlide(t);
101
+ n.replaceWith(s), this.splide.refresh();
102
+ } catch (e) {
103
+ const n = e instanceof Error ? e.message : String(e);
104
+ throw this.emit("error", { message: n }), new Error(`Failed to update slide: ${n}`);
105
+ }
106
+ }
107
+ /**
108
+ * 注册事件监听器
109
+ *
110
+ * @param event - 事件类型
111
+ * @param handler - 事件处理器
112
+ */
113
+ on(i, t) {
114
+ this.eventHandlers.has(i) || this.eventHandlers.set(i, /* @__PURE__ */ new Set()), this.eventHandlers.get(i).add(t);
115
+ }
116
+ /**
117
+ * 移除事件监听器
118
+ *
119
+ * @param event - 事件类型
120
+ * @param handler - 事件处理器
121
+ */
122
+ off(i, t) {
123
+ const e = this.eventHandlers.get(i);
124
+ e && e.delete(t);
125
+ }
126
+ /**
127
+ * 创建 Splide DOM 结构
128
+ *
129
+ * @param container - 容器元素
130
+ */
131
+ createSplideStructure(i) {
132
+ const t = document.createElement("div");
133
+ t.className = "splide";
134
+ const e = document.createElement("div");
135
+ e.className = "splide__track";
136
+ const n = document.createElement("ul");
137
+ n.className = "splide__list", e.appendChild(n), t.appendChild(e), i.appendChild(t), this.splideContainer = t, this.splideTrack = e, this.splideList = n;
138
+ }
139
+ /**
140
+ * 设置 Splide 事件监听
141
+ */
142
+ setupEventListeners() {
143
+ this.splide && this.splide.on("moved", (i, t) => {
144
+ this.emit("slideChanged", {
145
+ index: i,
146
+ previousIndex: t,
147
+ from: t,
148
+ to: i
149
+ });
150
+ });
151
+ }
152
+ /**
153
+ * 渲染单张幻灯片
154
+ *
155
+ * @param slide - 幻灯片定义
156
+ * @returns slide 元素
157
+ */
158
+ async renderSlide(i) {
159
+ const t = document.createElement("li");
160
+ if (t.className = "splide__slide", i.content.type === "dynamic") {
161
+ const e = await this.renderDynamicContent(i.content.component, i.content.props);
162
+ t.appendChild(e);
163
+ } else {
164
+ const e = this.renderTextContent(i.content.lines);
165
+ t.appendChild(e);
166
+ }
167
+ return t;
168
+ }
169
+ /**
170
+ * 渲染动态内容(Web Component)
171
+ *
172
+ * @param component - 组件名称
173
+ * @param props - 组件属性
174
+ * @returns 组件元素
175
+ */
176
+ async renderDynamicContent(i, t) {
177
+ const e = document.createElement(i);
178
+ for (const [n, s] of Object.entries(t))
179
+ typeof s == "string" || typeof s == "number" ? e.setAttribute(n, String(s)) : typeof s == "boolean" ? s && e.setAttribute(n, "") : e[n] = s;
180
+ return e;
181
+ }
182
+ /**
183
+ * 渲染文本内容
184
+ *
185
+ * 支持以下格式:
186
+ * - # 标题 -> h1
187
+ * - ## 标题 -> h2
188
+ * - ### 标题 -> h3
189
+ * - ![alt](url) -> img
190
+ * - - 列表项 -> ul/li
191
+ * - 普通文本 -> p
192
+ *
193
+ * @param lines - 文本行数组
194
+ * @returns 内容容器元素
195
+ */
196
+ renderTextContent(i) {
197
+ const t = document.createElement("div");
198
+ t.className = "slide-content";
199
+ let e = null;
200
+ for (const n of i) {
201
+ const s = n.trim();
202
+ if (!s) {
203
+ e = null;
204
+ continue;
205
+ }
206
+ if (s.startsWith("# ")) {
207
+ e = null;
208
+ const r = document.createElement("h1");
209
+ r.textContent = s.substring(2), t.appendChild(r);
210
+ continue;
211
+ }
212
+ if (s.startsWith("## ")) {
213
+ e = null;
214
+ const r = document.createElement("h2");
215
+ r.textContent = s.substring(3), t.appendChild(r);
216
+ continue;
217
+ }
218
+ if (s.startsWith("### ")) {
219
+ e = null;
220
+ const r = document.createElement("h3");
221
+ r.textContent = s.substring(4), t.appendChild(r);
222
+ continue;
223
+ }
224
+ const l = s.match(/^!\[(.*?)\]\((.*?)\)$/);
225
+ if (l) {
226
+ e = null;
227
+ const r = document.createElement("img");
228
+ r.alt = l[1], r.src = l[2], r.style.maxWidth = "80%", r.style.maxHeight = "500px", t.appendChild(r);
229
+ continue;
230
+ }
231
+ if (s.startsWith("- ")) {
232
+ e || (e = document.createElement("ul"), t.appendChild(e));
233
+ const r = document.createElement("li");
234
+ r.textContent = s.substring(2), e.appendChild(r);
235
+ continue;
236
+ }
237
+ e = null;
238
+ const d = document.createElement("p");
239
+ d.textContent = s, t.appendChild(d);
240
+ }
241
+ return t;
242
+ }
243
+ /**
244
+ * 触发事件
245
+ *
246
+ * @param event - 事件类型
247
+ * @param data - 事件数据
248
+ */
249
+ emit(i, t) {
250
+ const e = this.eventHandlers.get(i);
251
+ if (e)
252
+ for (const n of e)
253
+ try {
254
+ n(t);
255
+ } catch (s) {
256
+ console.error(`Error in ${i} handler:`, s);
257
+ }
258
+ }
259
+ }
260
+ async function C(o, i, t) {
261
+ const e = await c(o), n = p(e), s = new m(), l = new h({
262
+ container: t.container,
263
+ adapter: s,
264
+ adapterOptions: {
265
+ splideConfig: t.splideOptions
266
+ }
267
+ });
268
+ return await l.run(n, i), l;
269
+ }
270
+ export {
271
+ m as SplideAdapter,
272
+ C as createSlideRunner
273
+ };
274
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/adapter.ts","../src/runner.ts"],"sourcesContent":["/**\n * @slidejs/runner-splide - SplideAdapter 适配器实现\n *\n * 将 Slide DSL 渲染为 Splide 幻灯片\n */\n\nimport { Splide } from '@splidejs/splide';\nimport type { Options } from '@splidejs/splide';\nimport type { SlideDefinition } from '@slidejs/core';\nimport type { SlideAdapter, AdapterEvent, EventHandler } from '@slidejs/runner';\nimport type { SplideAdapterOptions } from './types';\n\n/**\n * Splide 适配器\n *\n * 实现 SlideAdapter 接口,将 SlideDefinition 渲染为 Splide 幻灯片\n */\nexport class SplideAdapter implements SlideAdapter {\n readonly name = 'splide';\n\n private splide?: Splide;\n private splideContainer?: HTMLElement;\n private splideTrack?: HTMLElement;\n private splideList?: HTMLElement;\n private eventHandlers: Map<AdapterEvent, Set<EventHandler>> = new Map();\n\n /**\n * 初始化 Splide 适配器\n *\n * @param container - 容器元素\n * @param options - Splide 选项\n */\n async initialize(container: HTMLElement, options?: SplideAdapterOptions): Promise<void> {\n try {\n // 创建 Splide DOM 结构\n this.createSplideStructure(container);\n\n // 初始化 Splide\n if (!this.splideContainer) {\n throw new Error('Splide container not created');\n }\n\n const splideConfig: Options = {\n // 默认配置\n type: 'slide',\n perPage: 1,\n perMove: 1,\n gap: '1rem',\n pagination: true,\n arrows: true,\n keyboard: 'global',\n ...options?.splideConfig,\n };\n\n this.splide = new Splide(this.splideContainer, splideConfig);\n this.splide.mount();\n\n // 等待初始化完成\n await new Promise<void>(resolve => {\n requestAnimationFrame(() => {\n resolve();\n });\n });\n\n // 设置事件监听\n this.setupEventListeners();\n\n // 触发 ready 事件\n this.emit('ready');\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.emit('error', { message: errorMessage });\n throw new Error(`Failed to initialize SplideAdapter: ${errorMessage}`);\n }\n }\n\n /**\n * 渲染幻灯片\n *\n * @param slides - 幻灯片定义数组\n */\n async render(slides: SlideDefinition[]): Promise<void> {\n if (!this.splideList || !this.splide) {\n throw new Error('SplideAdapter not initialized');\n }\n\n try {\n // 清空现有幻灯片\n this.splideList.innerHTML = '';\n\n // 渲染每张幻灯片\n for (const slide of slides) {\n const slideElement = await this.renderSlide(slide);\n this.splideList.appendChild(slideElement);\n }\n\n // 刷新 Splide(重新计算和更新)\n this.splide.refresh();\n\n // 触发 slideRendered 事件\n this.emit('slideRendered', { totalSlides: slides.length });\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.emit('error', { message: errorMessage });\n throw new Error(`Failed to render slides: ${errorMessage}`);\n }\n }\n\n /**\n * 销毁适配器\n */\n async destroy(): Promise<void> {\n if (this.splide) {\n this.splide.destroy();\n this.splide = undefined;\n }\n\n if (this.splideContainer) {\n this.splideContainer.innerHTML = '';\n this.splideContainer = undefined;\n }\n\n this.splideTrack = undefined;\n this.splideList = undefined;\n this.eventHandlers.clear();\n }\n\n /**\n * 导航到指定幻灯片\n *\n * @param index - 幻灯片索引\n */\n navigateTo(index: number): void {\n if (!this.splide) {\n throw new Error('SplideAdapter not initialized');\n }\n\n this.splide.go(index);\n }\n\n /**\n * 获取当前幻灯片索引\n */\n getCurrentIndex(): number {\n if (!this.splide) {\n return 0;\n }\n\n return this.splide.index;\n }\n\n /**\n * 获取幻灯片总数\n */\n getTotalSlides(): number {\n if (!this.splide) {\n return 0;\n }\n\n return this.splide.length;\n }\n\n /**\n * 更新指定幻灯片\n *\n * @param index - 幻灯片索引\n * @param slide - 新的幻灯片定义\n */\n async updateSlide(index: number, slide: SlideDefinition): Promise<void> {\n if (!this.splideList || !this.splide) {\n throw new Error('SplideAdapter not initialized');\n }\n\n try {\n // 获取指定索引的 slide 元素\n const slides = this.splideList.querySelectorAll('.splide__slide');\n const targetSlide = slides[index] as HTMLElement;\n\n if (!targetSlide) {\n throw new Error(`Slide at index ${index} not found`);\n }\n\n // 渲染新的幻灯片内容\n const newSlide = await this.renderSlide(slide);\n\n // 替换旧的 slide\n targetSlide.replaceWith(newSlide);\n\n // 刷新 Splide\n this.splide.refresh();\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.emit('error', { message: errorMessage });\n throw new Error(`Failed to update slide: ${errorMessage}`);\n }\n }\n\n /**\n * 注册事件监听器\n *\n * @param event - 事件类型\n * @param handler - 事件处理器\n */\n on(event: AdapterEvent, handler: EventHandler): void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler);\n }\n\n /**\n * 移除事件监听器\n *\n * @param event - 事件类型\n * @param handler - 事件处理器\n */\n off(event: AdapterEvent, handler: EventHandler): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.delete(handler);\n }\n }\n\n /**\n * 创建 Splide DOM 结构\n *\n * @param container - 容器元素\n */\n private createSplideStructure(container: HTMLElement): void {\n // 创建 .splide 容器\n const splideDiv = document.createElement('div');\n splideDiv.className = 'splide';\n\n // 创建 .splide__track 容器\n const trackDiv = document.createElement('div');\n trackDiv.className = 'splide__track';\n\n // 创建 .splide__list 容器\n const listUl = document.createElement('ul');\n listUl.className = 'splide__list';\n\n trackDiv.appendChild(listUl);\n splideDiv.appendChild(trackDiv);\n container.appendChild(splideDiv);\n\n this.splideContainer = splideDiv;\n this.splideTrack = trackDiv;\n this.splideList = listUl;\n }\n\n /**\n * 设置 Splide 事件监听\n */\n private setupEventListeners(): void {\n if (!this.splide) {\n return;\n }\n\n // 监听幻灯片切换事件\n this.splide.on('moved', (newIndex, prevIndex) => {\n this.emit('slideChanged', {\n index: newIndex,\n previousIndex: prevIndex,\n from: prevIndex,\n to: newIndex,\n });\n });\n }\n\n /**\n * 渲染单张幻灯片\n *\n * @param slide - 幻灯片定义\n * @returns slide 元素\n */\n private async renderSlide(slide: SlideDefinition): Promise<HTMLElement> {\n const slideElement = document.createElement('li');\n slideElement.className = 'splide__slide';\n\n // 渲染内容\n if (slide.content.type === 'dynamic') {\n // 动态内容(Web Components)\n const content = await this.renderDynamicContent(slide.content.component, slide.content.props);\n slideElement.appendChild(content);\n } else {\n // 静态文本内容\n const content = this.renderTextContent(slide.content.lines);\n slideElement.appendChild(content);\n }\n\n return slideElement;\n }\n\n /**\n * 渲染动态内容(Web Component)\n *\n * @param component - 组件名称\n * @param props - 组件属性\n * @returns 组件元素\n */\n private async renderDynamicContent(\n component: string,\n props: Record<string, unknown>\n ): Promise<HTMLElement> {\n // 创建 Web Component 元素\n const element = document.createElement(component);\n\n // 设置属性\n for (const [key, value] of Object.entries(props)) {\n if (typeof value === 'string' || typeof value === 'number') {\n // 字符串和数字 → HTML attributes\n element.setAttribute(key, String(value));\n } else if (typeof value === 'boolean') {\n // 布尔值 → HTML attributes(true 时设置空属性)\n if (value) {\n element.setAttribute(key, '');\n }\n } else {\n // 对象和数组 → JavaScript properties\n (element as Record<string, unknown>)[key] = value;\n }\n }\n\n return element;\n }\n\n /**\n * 渲染文本内容\n *\n * 支持以下格式:\n * - # 标题 -> h1\n * - ## 标题 -> h2\n * - ### 标题 -> h3\n * - ![alt](url) -> img\n * - - 列表项 -> ul/li\n * - 普通文本 -> p\n *\n * @param lines - 文本行数组\n * @returns 内容容器元素\n */\n private renderTextContent(lines: string[]): HTMLElement {\n const container = document.createElement('div');\n container.className = 'slide-content';\n\n let listContainer: HTMLUListElement | null = null;\n\n for (const line of lines) {\n const trimmedLine = line.trim();\n\n // 空行,结束列表\n if (!trimmedLine) {\n listContainer = null;\n continue;\n }\n\n // 标题 - # H1\n if (trimmedLine.startsWith('# ')) {\n listContainer = null;\n const h1 = document.createElement('h1');\n h1.textContent = trimmedLine.substring(2);\n container.appendChild(h1);\n continue;\n }\n\n // 标题 - ## H2\n if (trimmedLine.startsWith('## ')) {\n listContainer = null;\n const h2 = document.createElement('h2');\n h2.textContent = trimmedLine.substring(3);\n container.appendChild(h2);\n continue;\n }\n\n // 标题 - ### H3\n if (trimmedLine.startsWith('### ')) {\n listContainer = null;\n const h3 = document.createElement('h3');\n h3.textContent = trimmedLine.substring(4);\n container.appendChild(h3);\n continue;\n }\n\n // 图片 - ![alt](url)\n const imageMatch = trimmedLine.match(/^!\\[(.*?)\\]\\((.*?)\\)$/);\n if (imageMatch) {\n listContainer = null;\n const img = document.createElement('img');\n img.alt = imageMatch[1];\n img.src = imageMatch[2];\n img.style.maxWidth = '80%';\n img.style.maxHeight = '500px';\n container.appendChild(img);\n continue;\n }\n\n // 列表项 - - 项目\n if (trimmedLine.startsWith('- ')) {\n if (!listContainer) {\n listContainer = document.createElement('ul');\n container.appendChild(listContainer);\n }\n const li = document.createElement('li');\n li.textContent = trimmedLine.substring(2);\n listContainer.appendChild(li);\n continue;\n }\n\n // 普通文本\n listContainer = null;\n const p = document.createElement('p');\n p.textContent = trimmedLine;\n container.appendChild(p);\n }\n\n return container;\n }\n\n /**\n * 触发事件\n *\n * @param event - 事件类型\n * @param data - 事件数据\n */\n private emit(event: AdapterEvent, data?: unknown): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n for (const handler of handlers) {\n try {\n handler(data);\n } catch (error) {\n console.error(`Error in ${event} handler:`, error);\n }\n }\n }\n }\n}\n","/**\n * @slidejs/runner-splide - SlideRunner 工厂函数\n *\n * 提供创建配置好的 SlideRunner 实例的便捷方法\n */\n\nimport { parseSlideDSL, compile } from '@slidejs/dsl';\nimport { SlideRunner } from '@slidejs/runner';\nimport type { SlideContext } from '@slidejs/context';\nimport { SplideAdapter } from './adapter';\nimport type { SplideAdapterOptions } from './types';\n\n/**\n * SlideRunner 配置选项\n */\nexport interface SlideRunnerConfig {\n /**\n * 容器选择器或 HTMLElement\n */\n container: string | HTMLElement;\n\n /**\n * Splide 配置选项\n */\n splideOptions?: SplideAdapterOptions['splideConfig'];\n}\n\n/**\n * 从 DSL 源代码创建并运行 SlideRunner\n *\n * @example\n * ```typescript\n * import { createSlideRunner } from '@slidejs/runner-splide';\n *\n * const dslSource = `\n * present quiz \"demo\" {\n * rules {\n * rule start \"intro\" {\n * slide {\n * content text { \"Hello World!\" }\n * }\n * }\n * }\n * }\n * `;\n *\n * const context = { sourceType: 'quiz', sourceId: 'demo', items: [] };\n * const runner = await createSlideRunner(dslSource, context, {\n * container: '#app',\n * splideOptions: {\n * type: 'slide',\n * perPage: 1,\n * pagination: true,\n * arrows: true,\n * },\n * });\n * ```\n */\nexport async function createSlideRunner<TContext extends SlideContext = SlideContext>(\n dslSource: string,\n context: TContext,\n config: SlideRunnerConfig\n): Promise<SlideRunner<TContext>> {\n // 1. 解析 DSL\n const ast = await parseSlideDSL(dslSource);\n\n // 2. 编译为 SlideDSL\n const slideDSL = compile<TContext>(ast);\n\n // 3. 创建适配器和 Runner\n const adapter = new SplideAdapter();\n const runner = new SlideRunner<TContext>({\n container: config.container,\n adapter,\n adapterOptions: {\n splideConfig: config.splideOptions,\n },\n });\n\n // 4. 运行演示(这会初始化适配器并渲染幻灯片)\n await runner.run(slideDSL, context);\n\n // 注意:需要手动调用 runner.play() 来启动演示(导航到第一张幻灯片)\n // 返回 runner 以便用户可以控制演示\n return runner;\n}\n"],"names":["SplideAdapter","container","options","splideConfig","Splide","resolve","error","errorMessage","slides","slide","slideElement","index","targetSlide","newSlide","event","handler","handlers","splideDiv","trackDiv","listUl","newIndex","prevIndex","content","component","props","element","key","value","lines","listContainer","line","trimmedLine","h1","h2","h3","imageMatch","img","li","p","data","createSlideRunner","dslSource","context","config","ast","parseSlideDSL","slideDSL","compile","adapter","runner","SlideRunner"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@slidejs/runner-splide",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ },
14
+ "./style.css": "./dist/style.css"
15
+ },
16
+ "scripts": {
17
+ "build": "vite build",
18
+ "dev": "vite build --watch"
19
+ },
20
+ "dependencies": {
21
+ "@slidejs/context": "workspace:*",
22
+ "@slidejs/core": "workspace:*",
23
+ "@slidejs/dsl": "workspace:*",
24
+ "@slidejs/runner": "workspace:*",
25
+ "@splidejs/splide": "^4.1.3"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^20.10.0",
29
+ "typescript": "^5.3.3",
30
+ "vite": "^5.0.0",
31
+ "vite-plugin-dts": "^3.7.0",
32
+ "vitest": "^1.0.0"
33
+ }
34
+ }
package/src/adapter.ts ADDED
@@ -0,0 +1,436 @@
1
+ /**
2
+ * @slidejs/runner-splide - SplideAdapter 适配器实现
3
+ *
4
+ * 将 Slide DSL 渲染为 Splide 幻灯片
5
+ */
6
+
7
+ import { Splide } from '@splidejs/splide';
8
+ import type { Options } from '@splidejs/splide';
9
+ import type { SlideDefinition } from '@slidejs/core';
10
+ import type { SlideAdapter, AdapterEvent, EventHandler } from '@slidejs/runner';
11
+ import type { SplideAdapterOptions } from './types';
12
+
13
+ /**
14
+ * Splide 适配器
15
+ *
16
+ * 实现 SlideAdapter 接口,将 SlideDefinition 渲染为 Splide 幻灯片
17
+ */
18
+ export class SplideAdapter implements SlideAdapter {
19
+ readonly name = 'splide';
20
+
21
+ private splide?: Splide;
22
+ private splideContainer?: HTMLElement;
23
+ private splideTrack?: HTMLElement;
24
+ private splideList?: HTMLElement;
25
+ private eventHandlers: Map<AdapterEvent, Set<EventHandler>> = new Map();
26
+
27
+ /**
28
+ * 初始化 Splide 适配器
29
+ *
30
+ * @param container - 容器元素
31
+ * @param options - Splide 选项
32
+ */
33
+ async initialize(container: HTMLElement, options?: SplideAdapterOptions): Promise<void> {
34
+ try {
35
+ // 创建 Splide DOM 结构
36
+ this.createSplideStructure(container);
37
+
38
+ // 初始化 Splide
39
+ if (!this.splideContainer) {
40
+ throw new Error('Splide container not created');
41
+ }
42
+
43
+ const splideConfig: Options = {
44
+ // 默认配置
45
+ type: 'slide',
46
+ perPage: 1,
47
+ perMove: 1,
48
+ gap: '1rem',
49
+ pagination: true,
50
+ arrows: true,
51
+ keyboard: 'global',
52
+ ...options?.splideConfig,
53
+ };
54
+
55
+ this.splide = new Splide(this.splideContainer, splideConfig);
56
+ this.splide.mount();
57
+
58
+ // 等待初始化完成
59
+ await new Promise<void>(resolve => {
60
+ requestAnimationFrame(() => {
61
+ resolve();
62
+ });
63
+ });
64
+
65
+ // 设置事件监听
66
+ this.setupEventListeners();
67
+
68
+ // 触发 ready 事件
69
+ this.emit('ready');
70
+ } catch (error) {
71
+ const errorMessage = error instanceof Error ? error.message : String(error);
72
+ this.emit('error', { message: errorMessage });
73
+ throw new Error(`Failed to initialize SplideAdapter: ${errorMessage}`);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * 渲染幻灯片
79
+ *
80
+ * @param slides - 幻灯片定义数组
81
+ */
82
+ async render(slides: SlideDefinition[]): Promise<void> {
83
+ if (!this.splideList || !this.splide) {
84
+ throw new Error('SplideAdapter not initialized');
85
+ }
86
+
87
+ try {
88
+ // 清空现有幻灯片
89
+ this.splideList.innerHTML = '';
90
+
91
+ // 渲染每张幻灯片
92
+ for (const slide of slides) {
93
+ const slideElement = await this.renderSlide(slide);
94
+ this.splideList.appendChild(slideElement);
95
+ }
96
+
97
+ // 刷新 Splide(重新计算和更新)
98
+ this.splide.refresh();
99
+
100
+ // 触发 slideRendered 事件
101
+ this.emit('slideRendered', { totalSlides: slides.length });
102
+ } catch (error) {
103
+ const errorMessage = error instanceof Error ? error.message : String(error);
104
+ this.emit('error', { message: errorMessage });
105
+ throw new Error(`Failed to render slides: ${errorMessage}`);
106
+ }
107
+ }
108
+
109
+ /**
110
+ * 销毁适配器
111
+ */
112
+ async destroy(): Promise<void> {
113
+ if (this.splide) {
114
+ this.splide.destroy();
115
+ this.splide = undefined;
116
+ }
117
+
118
+ if (this.splideContainer) {
119
+ this.splideContainer.innerHTML = '';
120
+ this.splideContainer = undefined;
121
+ }
122
+
123
+ this.splideTrack = undefined;
124
+ this.splideList = undefined;
125
+ this.eventHandlers.clear();
126
+ }
127
+
128
+ /**
129
+ * 导航到指定幻灯片
130
+ *
131
+ * @param index - 幻灯片索引
132
+ */
133
+ navigateTo(index: number): void {
134
+ if (!this.splide) {
135
+ throw new Error('SplideAdapter not initialized');
136
+ }
137
+
138
+ this.splide.go(index);
139
+ }
140
+
141
+ /**
142
+ * 获取当前幻灯片索引
143
+ */
144
+ getCurrentIndex(): number {
145
+ if (!this.splide) {
146
+ return 0;
147
+ }
148
+
149
+ return this.splide.index;
150
+ }
151
+
152
+ /**
153
+ * 获取幻灯片总数
154
+ */
155
+ getTotalSlides(): number {
156
+ if (!this.splide) {
157
+ return 0;
158
+ }
159
+
160
+ return this.splide.length;
161
+ }
162
+
163
+ /**
164
+ * 更新指定幻灯片
165
+ *
166
+ * @param index - 幻灯片索引
167
+ * @param slide - 新的幻灯片定义
168
+ */
169
+ async updateSlide(index: number, slide: SlideDefinition): Promise<void> {
170
+ if (!this.splideList || !this.splide) {
171
+ throw new Error('SplideAdapter not initialized');
172
+ }
173
+
174
+ try {
175
+ // 获取指定索引的 slide 元素
176
+ const slides = this.splideList.querySelectorAll('.splide__slide');
177
+ const targetSlide = slides[index] as HTMLElement;
178
+
179
+ if (!targetSlide) {
180
+ throw new Error(`Slide at index ${index} not found`);
181
+ }
182
+
183
+ // 渲染新的幻灯片内容
184
+ const newSlide = await this.renderSlide(slide);
185
+
186
+ // 替换旧的 slide
187
+ targetSlide.replaceWith(newSlide);
188
+
189
+ // 刷新 Splide
190
+ this.splide.refresh();
191
+ } catch (error) {
192
+ const errorMessage = error instanceof Error ? error.message : String(error);
193
+ this.emit('error', { message: errorMessage });
194
+ throw new Error(`Failed to update slide: ${errorMessage}`);
195
+ }
196
+ }
197
+
198
+ /**
199
+ * 注册事件监听器
200
+ *
201
+ * @param event - 事件类型
202
+ * @param handler - 事件处理器
203
+ */
204
+ on(event: AdapterEvent, handler: EventHandler): void {
205
+ if (!this.eventHandlers.has(event)) {
206
+ this.eventHandlers.set(event, new Set());
207
+ }
208
+ this.eventHandlers.get(event)!.add(handler);
209
+ }
210
+
211
+ /**
212
+ * 移除事件监听器
213
+ *
214
+ * @param event - 事件类型
215
+ * @param handler - 事件处理器
216
+ */
217
+ off(event: AdapterEvent, handler: EventHandler): void {
218
+ const handlers = this.eventHandlers.get(event);
219
+ if (handlers) {
220
+ handlers.delete(handler);
221
+ }
222
+ }
223
+
224
+ /**
225
+ * 创建 Splide DOM 结构
226
+ *
227
+ * @param container - 容器元素
228
+ */
229
+ private createSplideStructure(container: HTMLElement): void {
230
+ // 创建 .splide 容器
231
+ const splideDiv = document.createElement('div');
232
+ splideDiv.className = 'splide';
233
+
234
+ // 创建 .splide__track 容器
235
+ const trackDiv = document.createElement('div');
236
+ trackDiv.className = 'splide__track';
237
+
238
+ // 创建 .splide__list 容器
239
+ const listUl = document.createElement('ul');
240
+ listUl.className = 'splide__list';
241
+
242
+ trackDiv.appendChild(listUl);
243
+ splideDiv.appendChild(trackDiv);
244
+ container.appendChild(splideDiv);
245
+
246
+ this.splideContainer = splideDiv;
247
+ this.splideTrack = trackDiv;
248
+ this.splideList = listUl;
249
+ }
250
+
251
+ /**
252
+ * 设置 Splide 事件监听
253
+ */
254
+ private setupEventListeners(): void {
255
+ if (!this.splide) {
256
+ return;
257
+ }
258
+
259
+ // 监听幻灯片切换事件
260
+ this.splide.on('moved', (newIndex, prevIndex) => {
261
+ this.emit('slideChanged', {
262
+ index: newIndex,
263
+ previousIndex: prevIndex,
264
+ from: prevIndex,
265
+ to: newIndex,
266
+ });
267
+ });
268
+ }
269
+
270
+ /**
271
+ * 渲染单张幻灯片
272
+ *
273
+ * @param slide - 幻灯片定义
274
+ * @returns slide 元素
275
+ */
276
+ private async renderSlide(slide: SlideDefinition): Promise<HTMLElement> {
277
+ const slideElement = document.createElement('li');
278
+ slideElement.className = 'splide__slide';
279
+
280
+ // 渲染内容
281
+ if (slide.content.type === 'dynamic') {
282
+ // 动态内容(Web Components)
283
+ const content = await this.renderDynamicContent(slide.content.component, slide.content.props);
284
+ slideElement.appendChild(content);
285
+ } else {
286
+ // 静态文本内容
287
+ const content = this.renderTextContent(slide.content.lines);
288
+ slideElement.appendChild(content);
289
+ }
290
+
291
+ return slideElement;
292
+ }
293
+
294
+ /**
295
+ * 渲染动态内容(Web Component)
296
+ *
297
+ * @param component - 组件名称
298
+ * @param props - 组件属性
299
+ * @returns 组件元素
300
+ */
301
+ private async renderDynamicContent(
302
+ component: string,
303
+ props: Record<string, unknown>
304
+ ): Promise<HTMLElement> {
305
+ // 创建 Web Component 元素
306
+ const element = document.createElement(component);
307
+
308
+ // 设置属性
309
+ for (const [key, value] of Object.entries(props)) {
310
+ if (typeof value === 'string' || typeof value === 'number') {
311
+ // 字符串和数字 → HTML attributes
312
+ element.setAttribute(key, String(value));
313
+ } else if (typeof value === 'boolean') {
314
+ // 布尔值 → HTML attributes(true 时设置空属性)
315
+ if (value) {
316
+ element.setAttribute(key, '');
317
+ }
318
+ } else {
319
+ // 对象和数组 → JavaScript properties
320
+ (element as Record<string, unknown>)[key] = value;
321
+ }
322
+ }
323
+
324
+ return element;
325
+ }
326
+
327
+ /**
328
+ * 渲染文本内容
329
+ *
330
+ * 支持以下格式:
331
+ * - # 标题 -> h1
332
+ * - ## 标题 -> h2
333
+ * - ### 标题 -> h3
334
+ * - ![alt](url) -> img
335
+ * - - 列表项 -> ul/li
336
+ * - 普通文本 -> p
337
+ *
338
+ * @param lines - 文本行数组
339
+ * @returns 内容容器元素
340
+ */
341
+ private renderTextContent(lines: string[]): HTMLElement {
342
+ const container = document.createElement('div');
343
+ container.className = 'slide-content';
344
+
345
+ let listContainer: HTMLUListElement | null = null;
346
+
347
+ for (const line of lines) {
348
+ const trimmedLine = line.trim();
349
+
350
+ // 空行,结束列表
351
+ if (!trimmedLine) {
352
+ listContainer = null;
353
+ continue;
354
+ }
355
+
356
+ // 标题 - # H1
357
+ if (trimmedLine.startsWith('# ')) {
358
+ listContainer = null;
359
+ const h1 = document.createElement('h1');
360
+ h1.textContent = trimmedLine.substring(2);
361
+ container.appendChild(h1);
362
+ continue;
363
+ }
364
+
365
+ // 标题 - ## H2
366
+ if (trimmedLine.startsWith('## ')) {
367
+ listContainer = null;
368
+ const h2 = document.createElement('h2');
369
+ h2.textContent = trimmedLine.substring(3);
370
+ container.appendChild(h2);
371
+ continue;
372
+ }
373
+
374
+ // 标题 - ### H3
375
+ if (trimmedLine.startsWith('### ')) {
376
+ listContainer = null;
377
+ const h3 = document.createElement('h3');
378
+ h3.textContent = trimmedLine.substring(4);
379
+ container.appendChild(h3);
380
+ continue;
381
+ }
382
+
383
+ // 图片 - ![alt](url)
384
+ const imageMatch = trimmedLine.match(/^!\[(.*?)\]\((.*?)\)$/);
385
+ if (imageMatch) {
386
+ listContainer = null;
387
+ const img = document.createElement('img');
388
+ img.alt = imageMatch[1];
389
+ img.src = imageMatch[2];
390
+ img.style.maxWidth = '80%';
391
+ img.style.maxHeight = '500px';
392
+ container.appendChild(img);
393
+ continue;
394
+ }
395
+
396
+ // 列表项 - - 项目
397
+ if (trimmedLine.startsWith('- ')) {
398
+ if (!listContainer) {
399
+ listContainer = document.createElement('ul');
400
+ container.appendChild(listContainer);
401
+ }
402
+ const li = document.createElement('li');
403
+ li.textContent = trimmedLine.substring(2);
404
+ listContainer.appendChild(li);
405
+ continue;
406
+ }
407
+
408
+ // 普通文本
409
+ listContainer = null;
410
+ const p = document.createElement('p');
411
+ p.textContent = trimmedLine;
412
+ container.appendChild(p);
413
+ }
414
+
415
+ return container;
416
+ }
417
+
418
+ /**
419
+ * 触发事件
420
+ *
421
+ * @param event - 事件类型
422
+ * @param data - 事件数据
423
+ */
424
+ private emit(event: AdapterEvent, data?: unknown): void {
425
+ const handlers = this.eventHandlers.get(event);
426
+ if (handlers) {
427
+ for (const handler of handlers) {
428
+ try {
429
+ handler(data);
430
+ } catch (error) {
431
+ console.error(`Error in ${event} handler:`, error);
432
+ }
433
+ }
434
+ }
435
+ }
436
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @slidejs/runner-splide - Splide 适配器
3
+ *
4
+ * 将 Slide DSL 渲染为 Splide 幻灯片
5
+ */
6
+
7
+ export { SplideAdapter } from './adapter';
8
+ export { createSlideRunner, type SlideRunnerConfig } from './runner';
9
+ export type { SplideAdapterOptions } from './types';
package/src/runner.ts ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @slidejs/runner-splide - SlideRunner 工厂函数
3
+ *
4
+ * 提供创建配置好的 SlideRunner 实例的便捷方法
5
+ */
6
+
7
+ import { parseSlideDSL, compile } from '@slidejs/dsl';
8
+ import { SlideRunner } from '@slidejs/runner';
9
+ import type { SlideContext } from '@slidejs/context';
10
+ import { SplideAdapter } from './adapter';
11
+ import type { SplideAdapterOptions } from './types';
12
+
13
+ /**
14
+ * SlideRunner 配置选项
15
+ */
16
+ export interface SlideRunnerConfig {
17
+ /**
18
+ * 容器选择器或 HTMLElement
19
+ */
20
+ container: string | HTMLElement;
21
+
22
+ /**
23
+ * Splide 配置选项
24
+ */
25
+ splideOptions?: SplideAdapterOptions['splideConfig'];
26
+ }
27
+
28
+ /**
29
+ * 从 DSL 源代码创建并运行 SlideRunner
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * import { createSlideRunner } from '@slidejs/runner-splide';
34
+ *
35
+ * const dslSource = `
36
+ * present quiz "demo" {
37
+ * rules {
38
+ * rule start "intro" {
39
+ * slide {
40
+ * content text { "Hello World!" }
41
+ * }
42
+ * }
43
+ * }
44
+ * }
45
+ * `;
46
+ *
47
+ * const context = { sourceType: 'quiz', sourceId: 'demo', items: [] };
48
+ * const runner = await createSlideRunner(dslSource, context, {
49
+ * container: '#app',
50
+ * splideOptions: {
51
+ * type: 'slide',
52
+ * perPage: 1,
53
+ * pagination: true,
54
+ * arrows: true,
55
+ * },
56
+ * });
57
+ * ```
58
+ */
59
+ export async function createSlideRunner<TContext extends SlideContext = SlideContext>(
60
+ dslSource: string,
61
+ context: TContext,
62
+ config: SlideRunnerConfig
63
+ ): Promise<SlideRunner<TContext>> {
64
+ // 1. 解析 DSL
65
+ const ast = await parseSlideDSL(dslSource);
66
+
67
+ // 2. 编译为 SlideDSL
68
+ const slideDSL = compile<TContext>(ast);
69
+
70
+ // 3. 创建适配器和 Runner
71
+ const adapter = new SplideAdapter();
72
+ const runner = new SlideRunner<TContext>({
73
+ container: config.container,
74
+ adapter,
75
+ adapterOptions: {
76
+ splideConfig: config.splideOptions,
77
+ },
78
+ });
79
+
80
+ // 4. 运行演示(这会初始化适配器并渲染幻灯片)
81
+ await runner.run(slideDSL, context);
82
+
83
+ // 注意:需要手动调用 runner.play() 来启动演示(导航到第一张幻灯片)
84
+ // 返回 runner 以便用户可以控制演示
85
+ return runner;
86
+ }
package/src/types.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @slidejs/runner-splide - 类型定义
3
+ *
4
+ * 定义 Splide 适配器的选项和配置
5
+ */
6
+
7
+ import type { Options } from '@splidejs/splide';
8
+ import type { AdapterOptions } from '@slidejs/runner';
9
+
10
+ /**
11
+ * SplideAdapter 选项
12
+ *
13
+ * Splide CSS 需要手动导入:
14
+ * ```typescript
15
+ * import '@splidejs/splide/css';
16
+ * ```
17
+ *
18
+ * 注意:只需要导入基础 CSS,主题是可选的。
19
+ */
20
+ export interface SplideAdapterOptions extends AdapterOptions {
21
+ /**
22
+ * Splide 配置选项
23
+ * @see https://splidejs.com/options/
24
+ */
25
+ splideConfig?: Options;
26
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "composite": true,
7
+ "declarationMap": true
8
+ },
9
+ "include": ["src/**/*"],
10
+ "exclude": ["**/*.test.ts", "dist", "node_modules"],
11
+ "references": [{ "path": "../core" }, { "path": "../runner" }]
12
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,33 @@
1
+ import { defineConfig } from 'vite';
2
+ import dts from 'vite-plugin-dts';
3
+ import { resolve } from 'path';
4
+
5
+ export default defineConfig({
6
+ build: {
7
+ lib: {
8
+ entry: resolve(__dirname, 'src/index.ts'),
9
+ name: 'SlideJsSplide',
10
+ formats: ['es', 'cjs'],
11
+ fileName: format => `index.${format === 'es' ? 'js' : 'cjs'}`,
12
+ },
13
+ rollupOptions: {
14
+ external: [
15
+ '@slidejs/core',
16
+ '@slidejs/runner',
17
+ '@slidejs/dsl',
18
+ '@slidejs/context',
19
+ '@splidejs/splide',
20
+ // Only externalize JS modules from splide, NOT CSS
21
+ /^@splidejs\/splide\/.*\.js$/,
22
+ ],
23
+ },
24
+ sourcemap: true,
25
+ },
26
+ plugins: [
27
+ dts({
28
+ include: ['src/**/*'],
29
+ exclude: ['**/*.test.ts'],
30
+ rollupTypes: true,
31
+ }),
32
+ ],
33
+ });