@flightdev/ui 2.0.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/.turbo/turbo-build.log +81 -0
- package/.turbo/turbo-lint.log +40 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/LICENSE +21 -0
- package/README.md +92 -0
- package/TESTING.md +124 -0
- package/dist/adapter-MMD-iHNx.d.ts +424 -0
- package/dist/adapters/tier-1/angular.d.ts +60 -0
- package/dist/adapters/tier-1/angular.js +2 -0
- package/dist/adapters/tier-1/index.d.ts +7 -0
- package/dist/adapters/tier-1/index.js +7 -0
- package/dist/adapters/tier-1/qwik.d.ts +55 -0
- package/dist/adapters/tier-1/qwik.js +2 -0
- package/dist/adapters/tier-1/react.d.ts +67 -0
- package/dist/adapters/tier-1/react.js +2 -0
- package/dist/adapters/tier-1/solid.d.ts +45 -0
- package/dist/adapters/tier-1/solid.js +2 -0
- package/dist/adapters/tier-1/svelte.d.ts +48 -0
- package/dist/adapters/tier-1/svelte.js +2 -0
- package/dist/adapters/tier-1/vue.d.ts +47 -0
- package/dist/adapters/tier-1/vue.js +2 -0
- package/dist/adapters/tier-2/index.d.ts +7 -0
- package/dist/adapters/tier-2/index.js +7 -0
- package/dist/adapters/tier-2/inferno.d.ts +31 -0
- package/dist/adapters/tier-2/inferno.js +2 -0
- package/dist/adapters/tier-2/lit.d.ts +34 -0
- package/dist/adapters/tier-2/lit.js +2 -0
- package/dist/adapters/tier-2/marko.d.ts +59 -0
- package/dist/adapters/tier-2/marko.js +2 -0
- package/dist/adapters/tier-2/mithril.d.ts +31 -0
- package/dist/adapters/tier-2/mithril.js +2 -0
- package/dist/adapters/tier-2/preact.d.ts +33 -0
- package/dist/adapters/tier-2/preact.js +2 -0
- package/dist/adapters/tier-2/stencil.d.ts +52 -0
- package/dist/adapters/tier-2/stencil.js +2 -0
- package/dist/adapters/tier-3/alpine.d.ts +73 -0
- package/dist/adapters/tier-3/alpine.js +2 -0
- package/dist/adapters/tier-3/hotwire.d.ts +71 -0
- package/dist/adapters/tier-3/hotwire.js +2 -0
- package/dist/adapters/tier-3/htmx.d.ts +88 -0
- package/dist/adapters/tier-3/htmx.js +2 -0
- package/dist/adapters/tier-3/index.d.ts +7 -0
- package/dist/adapters/tier-3/index.js +7 -0
- package/dist/adapters/tier-3/petite-vue.d.ts +56 -0
- package/dist/adapters/tier-3/petite-vue.js +2 -0
- package/dist/adapters/tier-3/stimulus.d.ts +63 -0
- package/dist/adapters/tier-3/stimulus.js +2 -0
- package/dist/adapters/tier-3/vanilla.d.ts +63 -0
- package/dist/adapters/tier-3/vanilla.js +2 -0
- package/dist/chunk-2SNQ6PTM.js +217 -0
- package/dist/chunk-3D4XMIZI.js +136 -0
- package/dist/chunk-3HU6GSQ4.js +125 -0
- package/dist/chunk-4PZDNFL7.js +148 -0
- package/dist/chunk-5IBLFTYL.js +114 -0
- package/dist/chunk-64JZJ7OK.js +142 -0
- package/dist/chunk-7ZJI3QU2.js +132 -0
- package/dist/chunk-CE4FJHQJ.js +133 -0
- package/dist/chunk-DTCAUBH5.js +87 -0
- package/dist/chunk-NTASPOHG.js +106 -0
- package/dist/chunk-OI2AMQLG.js +152 -0
- package/dist/chunk-Q7HUE44H.js +106 -0
- package/dist/chunk-QH3LOWXU.js +155 -0
- package/dist/chunk-QIVAK6BH.js +103 -0
- package/dist/chunk-V34XPVGK.js +103 -0
- package/dist/chunk-VK7ZPMO7.js +221 -0
- package/dist/chunk-X6CNUW6T.js +136 -0
- package/dist/chunk-XTDK7ME5.js +382 -0
- package/dist/chunk-YFGSHW5S.js +121 -0
- package/dist/chunk-ZAJVSE7J.js +90 -0
- package/dist/core/index.d.ts +161 -0
- package/dist/core/index.js +2 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +71 -0
- package/docs/ADAPTERS.md +946 -0
- package/docs/PATTERNS.md +836 -0
- package/package.json +229 -0
- package/src/adapters/tier-1/angular.ts +223 -0
- package/src/adapters/tier-1/index.ts +12 -0
- package/src/adapters/tier-1/qwik.ts +177 -0
- package/src/adapters/tier-1/react.ts +330 -0
- package/src/adapters/tier-1/solid.ts +222 -0
- package/src/adapters/tier-1/svelte.ts +211 -0
- package/src/adapters/tier-1/vue.ts +234 -0
- package/src/adapters/tier-2/index.ts +12 -0
- package/src/adapters/tier-2/inferno.ts +149 -0
- package/src/adapters/tier-2/lit.ts +191 -0
- package/src/adapters/tier-2/marko.ts +199 -0
- package/src/adapters/tier-2/mithril.ts +152 -0
- package/src/adapters/tier-2/preact.ts +133 -0
- package/src/adapters/tier-2/stencil.ts +214 -0
- package/src/adapters/tier-3/alpine.ts +218 -0
- package/src/adapters/tier-3/hotwire.ts +254 -0
- package/src/adapters/tier-3/htmx.ts +263 -0
- package/src/adapters/tier-3/index.ts +12 -0
- package/src/adapters/tier-3/petite-vue.ts +163 -0
- package/src/adapters/tier-3/stimulus.ts +233 -0
- package/src/adapters/tier-3/vanilla.ts +252 -0
- package/src/ambient.d.ts +310 -0
- package/src/core/adapter.ts +366 -0
- package/src/core/index.ts +56 -0
- package/src/core/registry.ts +518 -0
- package/src/core/types.ts +461 -0
- package/src/htmx.ts +134 -0
- package/src/index.ts +263 -0
- package/test/__mocks__/stencil-core.ts +19 -0
- package/test/__mocks__/stencil-hydrate.ts +15 -0
- package/test/adapters/tier-1.test.ts +206 -0
- package/test/adapters/tier-2.test.ts +175 -0
- package/test/adapters/tier-3.test.ts +284 -0
- package/test/contracts/adapter.contract.ts +293 -0
- package/test/core/core.test.ts +310 -0
- package/test/errors/error-handling.test.ts +454 -0
- package/test/integration/htmx.integration.test.ts +246 -0
- package/test/integration/react.integration.test.ts +271 -0
- package/test/integration/registry.integration.test.ts +308 -0
- package/tsconfig.json +22 -0
- package/tsup.config.ts +93 -0
- package/vitest.config.ts +101 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flightdev/ui - HTMX Adapter (Tier 3)
|
|
3
|
+
*
|
|
4
|
+
* HTMX adapter - HTML over the wire, server-driven UI.
|
|
5
|
+
*
|
|
6
|
+
* @module @flightdev/ui/htmx
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { BaseUIAdapter } from '../../core/adapter.js';
|
|
11
|
+
import type {
|
|
12
|
+
AdapterCapabilities,
|
|
13
|
+
Component,
|
|
14
|
+
RenderContext,
|
|
15
|
+
RenderResult,
|
|
16
|
+
} from '../../core/types.js';
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Types
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
export type HTMXExtension =
|
|
23
|
+
| 'json-enc'
|
|
24
|
+
| 'loading-states'
|
|
25
|
+
| 'class-tools'
|
|
26
|
+
| 'morphdom-swap'
|
|
27
|
+
| 'alpine-morph'
|
|
28
|
+
| 'path-deps'
|
|
29
|
+
| 'preload'
|
|
30
|
+
| 'remove-me'
|
|
31
|
+
| 'response-targets'
|
|
32
|
+
| 'head-support'
|
|
33
|
+
| 'sse'
|
|
34
|
+
| 'ws';
|
|
35
|
+
|
|
36
|
+
export interface HTMXAdapterOptions {
|
|
37
|
+
/** HTMX version */
|
|
38
|
+
version?: string;
|
|
39
|
+
|
|
40
|
+
/** Extensions to load */
|
|
41
|
+
extensions?: HTMXExtension[];
|
|
42
|
+
|
|
43
|
+
/** Enable WebSocket extension */
|
|
44
|
+
websocket?: boolean;
|
|
45
|
+
|
|
46
|
+
/** Enable SSE extension */
|
|
47
|
+
sse?: boolean;
|
|
48
|
+
|
|
49
|
+
/** CDN base URL */
|
|
50
|
+
cdnBase?: string;
|
|
51
|
+
|
|
52
|
+
/** Enable head element support */
|
|
53
|
+
headSupport?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// HTMX Adapter
|
|
58
|
+
// ============================================================================
|
|
59
|
+
|
|
60
|
+
export class HTMXAdapter extends BaseUIAdapter {
|
|
61
|
+
readonly id = 'htmx';
|
|
62
|
+
readonly name = 'HTMX';
|
|
63
|
+
readonly framework = 'htmx';
|
|
64
|
+
readonly frameworkVersion = '2.0+';
|
|
65
|
+
readonly tier = 'tier-3' as const;
|
|
66
|
+
|
|
67
|
+
override readonly capabilities: AdapterCapabilities = {
|
|
68
|
+
streaming: false,
|
|
69
|
+
partialHydration: false,
|
|
70
|
+
islands: false,
|
|
71
|
+
resumable: false,
|
|
72
|
+
ssg: true,
|
|
73
|
+
csr: false, // HTMX is server-driven
|
|
74
|
+
serverComponents: false,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
constructor(private options: HTMXAdapterOptions = {}) {
|
|
78
|
+
super();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async renderToString(
|
|
82
|
+
component: Component,
|
|
83
|
+
_context?: RenderContext
|
|
84
|
+
): Promise<RenderResult> {
|
|
85
|
+
const startTime = performance.now();
|
|
86
|
+
|
|
87
|
+
// HTMX components are pure HTML
|
|
88
|
+
let html: string;
|
|
89
|
+
|
|
90
|
+
if (typeof component.component === 'function') {
|
|
91
|
+
html = (component.component as (props: unknown) => string)(component.props ?? {});
|
|
92
|
+
} else if (typeof component.component === 'string') {
|
|
93
|
+
html = component.component;
|
|
94
|
+
} else {
|
|
95
|
+
throw new Error(
|
|
96
|
+
'[Flight/HTMX] Components must be HTML strings or template functions.\n' +
|
|
97
|
+
'HTMX is server-driven - use hx-* attributes for interactivity.'
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
html,
|
|
103
|
+
// No hydration data - HTMX is server-driven
|
|
104
|
+
timing: this.createTiming(startTime),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
override getHydrationScript(_result: RenderResult): string {
|
|
109
|
+
const {
|
|
110
|
+
version = '2.0.2',
|
|
111
|
+
extensions = [],
|
|
112
|
+
websocket = false,
|
|
113
|
+
sse = false,
|
|
114
|
+
cdnBase = 'https://unpkg.com',
|
|
115
|
+
headSupport = false,
|
|
116
|
+
} = this.options;
|
|
117
|
+
|
|
118
|
+
// Build extension list
|
|
119
|
+
const allExtensions = [
|
|
120
|
+
...extensions,
|
|
121
|
+
...(websocket ? ['ws'] : []),
|
|
122
|
+
...(sse ? ['sse'] : []),
|
|
123
|
+
...(headSupport ? ['head-support'] : []),
|
|
124
|
+
] as HTMXExtension[];
|
|
125
|
+
|
|
126
|
+
// Extension script tags
|
|
127
|
+
const extScripts = allExtensions
|
|
128
|
+
.map((ext) =>
|
|
129
|
+
`<script src="${cdnBase}/htmx.org@${version}/dist/ext/${ext}.js"></script>`
|
|
130
|
+
)
|
|
131
|
+
.join('\n');
|
|
132
|
+
|
|
133
|
+
return `
|
|
134
|
+
<!-- Flight/HTMX -->
|
|
135
|
+
<script src="${cdnBase}/htmx.org@${version}"></script>
|
|
136
|
+
${extScripts}
|
|
137
|
+
<script>
|
|
138
|
+
window.__FLIGHT_ADAPTER__ = 'htmx';
|
|
139
|
+
// HTMX is ready - server-driven UI active
|
|
140
|
+
</script>
|
|
141
|
+
`.trim();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
override getClientEntry(): string {
|
|
145
|
+
return `
|
|
146
|
+
// HTMX Client Entry
|
|
147
|
+
// HTMX requires no explicit client-side initialization
|
|
148
|
+
|
|
149
|
+
export function hydrate() {
|
|
150
|
+
// HTMX auto-initializes via script tag
|
|
151
|
+
// No JavaScript framework needed
|
|
152
|
+
console.log('[Flight/HTMX] Server-driven UI ready');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// HTMX event helpers
|
|
156
|
+
export function onLoad(callback) {
|
|
157
|
+
document.addEventListener('htmx:load', callback);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function onAfterSwap(callback) {
|
|
161
|
+
document.addEventListener('htmx:afterSwap', callback);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function onBeforeRequest(callback) {
|
|
165
|
+
document.addEventListener('htmx:beforeRequest', callback);
|
|
166
|
+
}
|
|
167
|
+
`.trim();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ============================================================================
|
|
172
|
+
// Template Helpers
|
|
173
|
+
// ============================================================================
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Create an hx-get element
|
|
177
|
+
*/
|
|
178
|
+
export function hxGet(
|
|
179
|
+
url: string,
|
|
180
|
+
options: { target?: string; swap?: string; trigger?: string } = {}
|
|
181
|
+
): Record<string, string> {
|
|
182
|
+
const attrs: Record<string, string> = { 'hx-get': url };
|
|
183
|
+
if (options.target) attrs['hx-target'] = options.target;
|
|
184
|
+
if (options.swap) attrs['hx-swap'] = options.swap;
|
|
185
|
+
if (options.trigger) attrs['hx-trigger'] = options.trigger;
|
|
186
|
+
return attrs;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Create an hx-post element
|
|
191
|
+
*/
|
|
192
|
+
export function hxPost(
|
|
193
|
+
url: string,
|
|
194
|
+
options: { target?: string; swap?: string; trigger?: string } = {}
|
|
195
|
+
): Record<string, string> {
|
|
196
|
+
const attrs: Record<string, string> = { 'hx-post': url };
|
|
197
|
+
if (options.target) attrs['hx-target'] = options.target;
|
|
198
|
+
if (options.swap) attrs['hx-swap'] = options.swap;
|
|
199
|
+
if (options.trigger) attrs['hx-trigger'] = options.trigger;
|
|
200
|
+
return attrs;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Create an hx-delete element
|
|
205
|
+
*/
|
|
206
|
+
export function hxDelete(
|
|
207
|
+
url: string,
|
|
208
|
+
options: { target?: string; swap?: string; confirm?: string } = {}
|
|
209
|
+
): Record<string, string> {
|
|
210
|
+
const attrs: Record<string, string> = { 'hx-delete': url };
|
|
211
|
+
if (options.target) attrs['hx-target'] = options.target;
|
|
212
|
+
if (options.swap) attrs['hx-swap'] = options.swap;
|
|
213
|
+
if (options.confirm) attrs['hx-confirm'] = options.confirm;
|
|
214
|
+
return attrs;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Convert attributes object to HTML string
|
|
219
|
+
*/
|
|
220
|
+
export function attrsToString(attrs: Record<string, string>): string {
|
|
221
|
+
return Object.entries(attrs)
|
|
222
|
+
.map(([k, v]) => `${k}="${v}"`)
|
|
223
|
+
.join(' ');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Create a simple HTML element with hx-* attributes
|
|
228
|
+
*/
|
|
229
|
+
export function hx(
|
|
230
|
+
tag: string,
|
|
231
|
+
attrs: Record<string, string>,
|
|
232
|
+
content?: string
|
|
233
|
+
): string {
|
|
234
|
+
const attrStr = attrsToString(attrs);
|
|
235
|
+
if (content !== undefined) {
|
|
236
|
+
return `<${tag} ${attrStr}>${content}</${tag}>`;
|
|
237
|
+
}
|
|
238
|
+
return `<${tag} ${attrStr} />`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ============================================================================
|
|
242
|
+
// Factory Function
|
|
243
|
+
// ============================================================================
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Create an HTMX UI adapter.
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```typescript
|
|
250
|
+
* import { htmx } from '@flightdev/ui/htmx';
|
|
251
|
+
* import { defineUI } from '@flightdev/ui';
|
|
252
|
+
*
|
|
253
|
+
* export default defineUI(htmx({
|
|
254
|
+
* extensions: ['json-enc', 'loading-states'],
|
|
255
|
+
* sse: true,
|
|
256
|
+
* }));
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
export function htmx(options?: HTMXAdapterOptions): HTMXAdapter {
|
|
260
|
+
return new HTMXAdapter(options);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export default htmx;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flightdev/ui - Tier 3 Adapters Index
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all Tier 3 (HTML-First) adapters.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { alpine, AlpineAdapter, xData, xFor, xIf, xBind, xOn, type AlpineAdapterOptions, type AlpinePlugin } from './alpine.js';
|
|
8
|
+
export { htmx, HTMXAdapter, hxGet, hxPost, hxDelete, hx, attrsToString, type HTMXAdapterOptions, type HTMXExtension } from './htmx.js';
|
|
9
|
+
export { hotwire, HotwireAdapter, turboFrame, turboStream, stimulusController, type HotwireAdapterOptions } from './hotwire.js';
|
|
10
|
+
export { stimulus, StimulusAdapter, controller, action, target, type StimulusAdapterOptions } from './stimulus.js';
|
|
11
|
+
export { petiteVue, PetiteVueAdapter, vScope, vFor, vIf, type PetiteVueAdapterOptions } from './petite-vue.js';
|
|
12
|
+
export { vanilla, VanillaAdapter, shadowRoot, slot, createElementClass, type VanillaAdapterOptions } from './vanilla.js';
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flightdev/ui - Petite-vue Adapter (Tier 3)
|
|
3
|
+
*
|
|
4
|
+
* Petite-vue adapter - minimal (~6kb) Vue subset for progressive enhancement.
|
|
5
|
+
*
|
|
6
|
+
* @module @flightdev/ui/petite-vue
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { BaseUIAdapter } from '../../core/adapter.js';
|
|
11
|
+
import type {
|
|
12
|
+
AdapterCapabilities,
|
|
13
|
+
Component,
|
|
14
|
+
RenderContext,
|
|
15
|
+
RenderResult,
|
|
16
|
+
} from '../../core/types.js';
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Types
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
export interface PetiteVueAdapterOptions {
|
|
23
|
+
/** Petite-vue version */
|
|
24
|
+
version?: string;
|
|
25
|
+
|
|
26
|
+
/** Auto-initialize with v-scope */
|
|
27
|
+
autoInit?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Petite-vue Adapter
|
|
32
|
+
// ============================================================================
|
|
33
|
+
|
|
34
|
+
export class PetiteVueAdapter extends BaseUIAdapter {
|
|
35
|
+
readonly id = 'petite-vue';
|
|
36
|
+
readonly name = 'Petite-vue';
|
|
37
|
+
readonly framework = 'petite-vue';
|
|
38
|
+
readonly frameworkVersion = '0.4+';
|
|
39
|
+
readonly tier = 'tier-3' as const;
|
|
40
|
+
|
|
41
|
+
override readonly capabilities: AdapterCapabilities = {
|
|
42
|
+
streaming: false,
|
|
43
|
+
partialHydration: false,
|
|
44
|
+
islands: false,
|
|
45
|
+
resumable: false,
|
|
46
|
+
ssg: true,
|
|
47
|
+
csr: true,
|
|
48
|
+
serverComponents: false,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
constructor(private options: PetiteVueAdapterOptions = {}) {
|
|
52
|
+
super();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async renderToString(
|
|
56
|
+
component: Component,
|
|
57
|
+
_context?: RenderContext
|
|
58
|
+
): Promise<RenderResult> {
|
|
59
|
+
const startTime = performance.now();
|
|
60
|
+
|
|
61
|
+
let html: string;
|
|
62
|
+
|
|
63
|
+
if (typeof component.component === 'function') {
|
|
64
|
+
html = (component.component as (props: unknown) => string)(component.props ?? {});
|
|
65
|
+
} else if (typeof component.component === 'string') {
|
|
66
|
+
html = component.component;
|
|
67
|
+
} else {
|
|
68
|
+
throw new Error(
|
|
69
|
+
'[Flight/Petite-vue] Components must be HTML strings or template functions.\n' +
|
|
70
|
+
'Use v-scope for state and @click, v-model, etc. for interactivity.'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
html,
|
|
76
|
+
hydrationData: component.props,
|
|
77
|
+
timing: this.createTiming(startTime),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
override getHydrationScript(_result: RenderResult): string {
|
|
82
|
+
const {
|
|
83
|
+
version = '0.4.1',
|
|
84
|
+
autoInit = true,
|
|
85
|
+
} = this.options;
|
|
86
|
+
|
|
87
|
+
return `
|
|
88
|
+
<!-- Flight/Petite-vue -->
|
|
89
|
+
<script type="module">
|
|
90
|
+
import { createApp } from 'https://esm.sh/petite-vue@${version}';
|
|
91
|
+
|
|
92
|
+
window.__FLIGHT_ADAPTER__ = 'petite-vue';
|
|
93
|
+
${autoInit ? `createApp().mount();` : '// Manual init: createApp().mount()'}
|
|
94
|
+
|
|
95
|
+
console.log('[Flight/Petite-vue] Initialized');
|
|
96
|
+
</script>
|
|
97
|
+
`.trim();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
override getClientEntry(): string {
|
|
101
|
+
return `
|
|
102
|
+
import { createApp, reactive } from 'petite-vue';
|
|
103
|
+
|
|
104
|
+
export { createApp, reactive };
|
|
105
|
+
|
|
106
|
+
export function hydrate() {
|
|
107
|
+
createApp().mount();
|
|
108
|
+
console.log('[Flight/Petite-vue] Application mounted');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function createScope(initialState) {
|
|
112
|
+
return reactive(initialState);
|
|
113
|
+
}
|
|
114
|
+
`.trim();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ============================================================================
|
|
119
|
+
// Template Helpers
|
|
120
|
+
// ============================================================================
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Create a v-scope element
|
|
124
|
+
*/
|
|
125
|
+
export function vScope(state: Record<string, unknown>, content: string): string {
|
|
126
|
+
const stateStr = JSON.stringify(state).replace(/"/g, "'");
|
|
127
|
+
return `<div v-scope="${stateStr}">${content}</div>`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Create v-for binding
|
|
132
|
+
*/
|
|
133
|
+
export function vFor(items: string, itemName: string, content: string): string {
|
|
134
|
+
return `<template v-for="${itemName} in ${items}">${content}</template>`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Create v-if binding
|
|
139
|
+
*/
|
|
140
|
+
export function vIf(condition: string, content: string): string {
|
|
141
|
+
return `<template v-if="${condition}">${content}</template>`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ============================================================================
|
|
145
|
+
// Factory Function
|
|
146
|
+
// ============================================================================
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Create a Petite-vue UI adapter.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* import { petiteVue } from '@flightdev/ui/petite-vue';
|
|
154
|
+
* import { defineUI } from '@flightdev/ui';
|
|
155
|
+
*
|
|
156
|
+
* export default defineUI(petiteVue());
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
export function petiteVue(options?: PetiteVueAdapterOptions): PetiteVueAdapter {
|
|
160
|
+
return new PetiteVueAdapter(options);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export default petiteVue;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flightdev/ui - Stimulus Adapter (Tier 3)
|
|
3
|
+
*
|
|
4
|
+
* Stimulus adapter - modest JavaScript for the HTML you already have.
|
|
5
|
+
*
|
|
6
|
+
* @module @flightdev/ui/stimulus
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { BaseUIAdapter } from '../../core/adapter.js';
|
|
11
|
+
import type {
|
|
12
|
+
AdapterCapabilities,
|
|
13
|
+
Component,
|
|
14
|
+
RenderContext,
|
|
15
|
+
RenderResult,
|
|
16
|
+
} from '../../core/types.js';
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Types
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
export interface StimulusAdapterOptions {
|
|
23
|
+
/** Stimulus version */
|
|
24
|
+
version?: string;
|
|
25
|
+
|
|
26
|
+
/** Controllers directory path */
|
|
27
|
+
controllersPath?: string;
|
|
28
|
+
|
|
29
|
+
/** Enable debug mode */
|
|
30
|
+
debug?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Stimulus Adapter
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
export class StimulusAdapter extends BaseUIAdapter {
|
|
38
|
+
readonly id = 'stimulus';
|
|
39
|
+
readonly name = 'Stimulus';
|
|
40
|
+
readonly framework = 'stimulus';
|
|
41
|
+
readonly frameworkVersion = '3+';
|
|
42
|
+
readonly tier = 'tier-3' as const;
|
|
43
|
+
|
|
44
|
+
override readonly capabilities: AdapterCapabilities = {
|
|
45
|
+
streaming: false,
|
|
46
|
+
partialHydration: false,
|
|
47
|
+
islands: false,
|
|
48
|
+
resumable: false,
|
|
49
|
+
ssg: true,
|
|
50
|
+
csr: true,
|
|
51
|
+
serverComponents: false,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
constructor(private options: StimulusAdapterOptions = {}) {
|
|
55
|
+
super();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async renderToString(
|
|
59
|
+
component: Component,
|
|
60
|
+
_context?: RenderContext
|
|
61
|
+
): Promise<RenderResult> {
|
|
62
|
+
const startTime = performance.now();
|
|
63
|
+
|
|
64
|
+
let html: string;
|
|
65
|
+
|
|
66
|
+
if (typeof component.component === 'function') {
|
|
67
|
+
html = (component.component as (props: unknown) => string)(component.props ?? {});
|
|
68
|
+
} else if (typeof component.component === 'string') {
|
|
69
|
+
html = component.component;
|
|
70
|
+
} else {
|
|
71
|
+
throw new Error(
|
|
72
|
+
'[Flight/Stimulus] Components must be HTML strings or template functions.\n' +
|
|
73
|
+
'Use data-controller and data-action for interactivity.'
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
html,
|
|
79
|
+
hydrationData: component.props,
|
|
80
|
+
timing: this.createTiming(startTime),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
override getHydrationScript(_result: RenderResult): string {
|
|
85
|
+
const {
|
|
86
|
+
version = '3.2.2',
|
|
87
|
+
debug = false,
|
|
88
|
+
} = this.options;
|
|
89
|
+
|
|
90
|
+
return `
|
|
91
|
+
<!-- Flight/Stimulus -->
|
|
92
|
+
<script type="module">
|
|
93
|
+
import { Application } from 'https://esm.sh/@hotwired/stimulus@${version}';
|
|
94
|
+
|
|
95
|
+
window.Stimulus = Application.start();
|
|
96
|
+
${debug ? 'window.Stimulus.debug = true;' : ''}
|
|
97
|
+
|
|
98
|
+
window.__FLIGHT_ADAPTER__ = 'stimulus';
|
|
99
|
+
console.log('[Flight/Stimulus] Application started');
|
|
100
|
+
</script>
|
|
101
|
+
`.trim();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
override getClientEntry(): string {
|
|
105
|
+
return `
|
|
106
|
+
import { Application, Controller } from '@hotwired/stimulus';
|
|
107
|
+
|
|
108
|
+
// Start Stimulus application
|
|
109
|
+
export const application = Application.start();
|
|
110
|
+
|
|
111
|
+
// Export Controller for extension
|
|
112
|
+
export { Controller };
|
|
113
|
+
|
|
114
|
+
// Register controller helper
|
|
115
|
+
export function register(name, controller) {
|
|
116
|
+
application.register(name, controller);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Bulk register controllers
|
|
120
|
+
export function registerControllers(controllers) {
|
|
121
|
+
for (const [name, controller] of Object.entries(controllers)) {
|
|
122
|
+
register(name, controller);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Dynamic controller loading via import.meta.glob (Vite)
|
|
127
|
+
export function eagerLoadControllers(controllerModules) {
|
|
128
|
+
for (const [path, module] of Object.entries(controllerModules)) {
|
|
129
|
+
const match = path.match(/\\/([^/]+)_controller\\.[jt]s$/);
|
|
130
|
+
if (match && module.default) {
|
|
131
|
+
const name = match[1].replace(/_/g, '-');
|
|
132
|
+
register(name, module.default);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Default hydration
|
|
138
|
+
export function hydrate() {
|
|
139
|
+
console.log('[Flight/Stimulus] Controllers ready');
|
|
140
|
+
}
|
|
141
|
+
`.trim();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ============================================================================
|
|
146
|
+
// Template Helpers
|
|
147
|
+
// ============================================================================
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Create an element with a Stimulus controller
|
|
151
|
+
*/
|
|
152
|
+
export function controller(
|
|
153
|
+
name: string,
|
|
154
|
+
content: string,
|
|
155
|
+
options: {
|
|
156
|
+
values?: Record<string, unknown>;
|
|
157
|
+
classes?: Record<string, string>;
|
|
158
|
+
outlets?: Record<string, string>;
|
|
159
|
+
targets?: string[];
|
|
160
|
+
} = {}
|
|
161
|
+
): string {
|
|
162
|
+
const attrs: string[] = [`data-controller="${name}"`];
|
|
163
|
+
|
|
164
|
+
// Values
|
|
165
|
+
if (options.values) {
|
|
166
|
+
for (const [key, value] of Object.entries(options.values)) {
|
|
167
|
+
attrs.push(`data-${name}-${toKebab(key)}-value="${value}"`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Classes
|
|
172
|
+
if (options.classes) {
|
|
173
|
+
for (const [key, value] of Object.entries(options.classes)) {
|
|
174
|
+
attrs.push(`data-${name}-${toKebab(key)}-class="${value}"`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Outlets
|
|
179
|
+
if (options.outlets) {
|
|
180
|
+
for (const [key, selector] of Object.entries(options.outlets)) {
|
|
181
|
+
attrs.push(`data-${name}-${toKebab(key)}-outlet="${selector}"`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return `<div ${attrs.join(' ')}>${content}</div>`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Create a data-action attribute
|
|
190
|
+
*/
|
|
191
|
+
export function action(
|
|
192
|
+
controller: string,
|
|
193
|
+
method: string,
|
|
194
|
+
event?: string
|
|
195
|
+
): string {
|
|
196
|
+
const eventPart = event ? `${event}->` : '';
|
|
197
|
+
return `data-action="${eventPart}${controller}#${method}"`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Create a data-target attribute
|
|
202
|
+
*/
|
|
203
|
+
export function target(controller: string, name: string): string {
|
|
204
|
+
return `data-${controller}-target="${name}"`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Convert camelCase to kebab-case
|
|
209
|
+
*/
|
|
210
|
+
function toKebab(str: string): string {
|
|
211
|
+
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ============================================================================
|
|
215
|
+
// Factory Function
|
|
216
|
+
// ============================================================================
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Create a Stimulus UI adapter.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```typescript
|
|
223
|
+
* import { stimulus } from '@flightdev/ui/stimulus';
|
|
224
|
+
* import { defineUI } from '@flightdev/ui';
|
|
225
|
+
*
|
|
226
|
+
* export default defineUI(stimulus({ debug: true }));
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
export function stimulus(options?: StimulusAdapterOptions): StimulusAdapter {
|
|
230
|
+
return new StimulusAdapter(options);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export default stimulus;
|