@flightdev/ui 2.0.1 → 4.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/README.md +283 -68
- package/dist/{chunk-XTDK7ME5.js → chunk-S4DTUQII.js} +246 -19
- package/dist/chunk-S4DTUQII.js.map +1 -0
- package/dist/core/index.d.ts +423 -3
- package/dist/core/index.js +23 -2
- package/dist/core/index.js.map +1 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.js +29 -5
- package/dist/index.js.map +1 -0
- package/package.json +7 -183
- package/.turbo/turbo-build.log +0 -81
- package/.turbo/turbo-lint.log +0 -40
- package/.turbo/turbo-typecheck.log +0 -4
- package/TESTING.md +0 -124
- package/dist/adapter-MMD-iHNx.d.ts +0 -424
- package/dist/adapters/tier-1/angular.d.ts +0 -60
- package/dist/adapters/tier-1/angular.js +0 -2
- package/dist/adapters/tier-1/index.d.ts +0 -7
- package/dist/adapters/tier-1/index.js +0 -7
- package/dist/adapters/tier-1/qwik.d.ts +0 -55
- package/dist/adapters/tier-1/qwik.js +0 -2
- package/dist/adapters/tier-1/react.d.ts +0 -67
- package/dist/adapters/tier-1/react.js +0 -2
- package/dist/adapters/tier-1/solid.d.ts +0 -45
- package/dist/adapters/tier-1/solid.js +0 -2
- package/dist/adapters/tier-1/svelte.d.ts +0 -48
- package/dist/adapters/tier-1/svelte.js +0 -2
- package/dist/adapters/tier-1/vue.d.ts +0 -47
- package/dist/adapters/tier-1/vue.js +0 -2
- package/dist/adapters/tier-2/index.d.ts +0 -7
- package/dist/adapters/tier-2/index.js +0 -7
- package/dist/adapters/tier-2/inferno.d.ts +0 -31
- package/dist/adapters/tier-2/inferno.js +0 -2
- package/dist/adapters/tier-2/lit.d.ts +0 -34
- package/dist/adapters/tier-2/lit.js +0 -2
- package/dist/adapters/tier-2/marko.d.ts +0 -59
- package/dist/adapters/tier-2/marko.js +0 -2
- package/dist/adapters/tier-2/mithril.d.ts +0 -31
- package/dist/adapters/tier-2/mithril.js +0 -2
- package/dist/adapters/tier-2/preact.d.ts +0 -33
- package/dist/adapters/tier-2/preact.js +0 -2
- package/dist/adapters/tier-2/stencil.d.ts +0 -52
- package/dist/adapters/tier-2/stencil.js +0 -2
- package/dist/adapters/tier-3/alpine.d.ts +0 -73
- package/dist/adapters/tier-3/alpine.js +0 -2
- package/dist/adapters/tier-3/hotwire.d.ts +0 -71
- package/dist/adapters/tier-3/hotwire.js +0 -2
- package/dist/adapters/tier-3/htmx.d.ts +0 -88
- package/dist/adapters/tier-3/htmx.js +0 -2
- package/dist/adapters/tier-3/index.d.ts +0 -7
- package/dist/adapters/tier-3/index.js +0 -7
- package/dist/adapters/tier-3/petite-vue.d.ts +0 -56
- package/dist/adapters/tier-3/petite-vue.js +0 -2
- package/dist/adapters/tier-3/stimulus.d.ts +0 -63
- package/dist/adapters/tier-3/stimulus.js +0 -2
- package/dist/adapters/tier-3/vanilla.d.ts +0 -63
- package/dist/adapters/tier-3/vanilla.js +0 -2
- package/dist/chunk-2SNQ6PTM.js +0 -217
- package/dist/chunk-3D4XMIZI.js +0 -136
- package/dist/chunk-3HU6GSQ4.js +0 -125
- package/dist/chunk-4PZDNFL7.js +0 -148
- package/dist/chunk-5IBLFTYL.js +0 -114
- package/dist/chunk-64JZJ7OK.js +0 -142
- package/dist/chunk-7ZJI3QU2.js +0 -132
- package/dist/chunk-CE4FJHQJ.js +0 -133
- package/dist/chunk-DTCAUBH5.js +0 -87
- package/dist/chunk-NTASPOHG.js +0 -106
- package/dist/chunk-OI2AMQLG.js +0 -152
- package/dist/chunk-Q7HUE44H.js +0 -106
- package/dist/chunk-QH3LOWXU.js +0 -155
- package/dist/chunk-QIVAK6BH.js +0 -103
- package/dist/chunk-V34XPVGK.js +0 -103
- package/dist/chunk-VK7ZPMO7.js +0 -221
- package/dist/chunk-X6CNUW6T.js +0 -136
- package/dist/chunk-YFGSHW5S.js +0 -121
- package/dist/chunk-ZAJVSE7J.js +0 -90
- package/docs/ADAPTERS.md +0 -946
- package/docs/PATTERNS.md +0 -836
- package/src/adapters/tier-1/angular.ts +0 -223
- package/src/adapters/tier-1/index.ts +0 -12
- package/src/adapters/tier-1/qwik.ts +0 -177
- package/src/adapters/tier-1/react.ts +0 -330
- package/src/adapters/tier-1/solid.ts +0 -222
- package/src/adapters/tier-1/svelte.ts +0 -211
- package/src/adapters/tier-1/vue.ts +0 -234
- package/src/adapters/tier-2/index.ts +0 -12
- package/src/adapters/tier-2/inferno.ts +0 -149
- package/src/adapters/tier-2/lit.ts +0 -191
- package/src/adapters/tier-2/marko.ts +0 -199
- package/src/adapters/tier-2/mithril.ts +0 -152
- package/src/adapters/tier-2/preact.ts +0 -133
- package/src/adapters/tier-2/stencil.ts +0 -214
- package/src/adapters/tier-3/alpine.ts +0 -218
- package/src/adapters/tier-3/hotwire.ts +0 -254
- package/src/adapters/tier-3/htmx.ts +0 -263
- package/src/adapters/tier-3/index.ts +0 -12
- package/src/adapters/tier-3/petite-vue.ts +0 -163
- package/src/adapters/tier-3/stimulus.ts +0 -233
- package/src/adapters/tier-3/vanilla.ts +0 -252
- package/src/ambient.d.ts +0 -310
- package/src/core/adapter.ts +0 -366
- package/src/core/index.ts +0 -56
- package/src/core/registry.ts +0 -518
- package/src/core/types.ts +0 -461
- package/src/htmx.ts +0 -134
- package/src/index.ts +0 -263
- package/test/__mocks__/stencil-core.ts +0 -19
- package/test/__mocks__/stencil-hydrate.ts +0 -15
- package/test/adapters/tier-1.test.ts +0 -206
- package/test/adapters/tier-2.test.ts +0 -175
- package/test/adapters/tier-3.test.ts +0 -284
- package/test/contracts/adapter.contract.ts +0 -293
- package/test/core/core.test.ts +0 -310
- package/test/errors/error-handling.test.ts +0 -454
- package/test/integration/htmx.integration.test.ts +0 -246
- package/test/integration/react.integration.test.ts +0 -271
- package/test/integration/registry.integration.test.ts +0 -308
- package/tsconfig.json +0 -22
- package/tsup.config.ts +0 -93
- package/vitest.config.ts +0 -101
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @flightdev/ui - Stencil Adapter (Tier 2)
|
|
3
|
-
*
|
|
4
|
-
* Stencil Web Components SSR adapter with declarative shadow DOM.
|
|
5
|
-
*
|
|
6
|
-
* @module @flightdev/ui/stencil
|
|
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
|
-
Island,
|
|
17
|
-
IslandOptions,
|
|
18
|
-
} from '../../core/types.js';
|
|
19
|
-
|
|
20
|
-
// ============================================================================
|
|
21
|
-
// Types
|
|
22
|
-
// ============================================================================
|
|
23
|
-
|
|
24
|
-
export interface StencilAdapterOptions {
|
|
25
|
-
/** Enable declarative shadow DOM */
|
|
26
|
-
declarativeShadowDom?: boolean;
|
|
27
|
-
|
|
28
|
-
/** Pretty print HTML output */
|
|
29
|
-
prettyHtml?: boolean;
|
|
30
|
-
|
|
31
|
-
/** Hydrate app ID */
|
|
32
|
-
hydrateAppId?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ============================================================================
|
|
36
|
-
// Stencil Adapter
|
|
37
|
-
// ============================================================================
|
|
38
|
-
|
|
39
|
-
export class StencilAdapter extends BaseUIAdapter {
|
|
40
|
-
readonly id = 'stencil';
|
|
41
|
-
readonly name = 'Stencil';
|
|
42
|
-
readonly framework = 'stencil';
|
|
43
|
-
readonly frameworkVersion = '4+';
|
|
44
|
-
readonly tier = 'tier-2' as const;
|
|
45
|
-
|
|
46
|
-
override readonly capabilities: AdapterCapabilities = {
|
|
47
|
-
streaming: false,
|
|
48
|
-
partialHydration: true,
|
|
49
|
-
islands: true, // Web Components are natural islands
|
|
50
|
-
resumable: false,
|
|
51
|
-
ssg: true,
|
|
52
|
-
csr: true,
|
|
53
|
-
serverComponents: false,
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
constructor(private options: StencilAdapterOptions = {}) {
|
|
57
|
-
super();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async renderToString(
|
|
61
|
-
component: Component,
|
|
62
|
-
_context?: RenderContext
|
|
63
|
-
): Promise<RenderResult> {
|
|
64
|
-
const startTime = performance.now();
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
// Stencil provides a hydrate package
|
|
68
|
-
const hydrateModule = await import('@stencil/core/hydrate');
|
|
69
|
-
const { renderToString } = hydrateModule;
|
|
70
|
-
|
|
71
|
-
// Component can be a tag name string or a full HTML string
|
|
72
|
-
let htmlInput: string;
|
|
73
|
-
|
|
74
|
-
if (typeof component.component === 'string') {
|
|
75
|
-
const tagName = component.component;
|
|
76
|
-
const props = component.props ?? {};
|
|
77
|
-
|
|
78
|
-
// Build attributes from props
|
|
79
|
-
const attrs = Object.entries(props)
|
|
80
|
-
.map(([key, value]) => {
|
|
81
|
-
// Convert camelCase to kebab-case for attributes
|
|
82
|
-
const attrName = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
83
|
-
return `${attrName}="${this.escapeHtml(String(value))}"`;
|
|
84
|
-
})
|
|
85
|
-
.join(' ');
|
|
86
|
-
|
|
87
|
-
htmlInput = `<${tagName} ${attrs}></${tagName}>`;
|
|
88
|
-
} else {
|
|
89
|
-
throw new Error('Stencil components must be specified as tag name strings');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const results = await renderToString(htmlInput, {
|
|
93
|
-
fullDocument: false,
|
|
94
|
-
prettyHtml: this.options.prettyHtml ?? false,
|
|
95
|
-
serializeShadowRoot: this.options.declarativeShadowDom
|
|
96
|
-
? 'declarative-shadow-dom'
|
|
97
|
-
: undefined,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
html: results.html,
|
|
102
|
-
hydrationData: {
|
|
103
|
-
tagName: component.component,
|
|
104
|
-
props: component.props,
|
|
105
|
-
componentId: component.id ?? this.generateId(),
|
|
106
|
-
},
|
|
107
|
-
timing: this.createTiming(startTime),
|
|
108
|
-
};
|
|
109
|
-
} catch (error) {
|
|
110
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
111
|
-
|
|
112
|
-
if (message.includes("Cannot find module '@stencil/core/hydrate'")) {
|
|
113
|
-
throw new Error(
|
|
114
|
-
'[Flight/Stencil] Stencil hydrate package not found.\n' +
|
|
115
|
-
'Install required packages:\n' +
|
|
116
|
-
' npm install @stencil/core\n' +
|
|
117
|
-
'And ensure your Stencil project has hydrate output configured.'
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
throw error;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Create an island from a Stencil Web Component
|
|
127
|
-
*/
|
|
128
|
-
override createIsland(
|
|
129
|
-
component: unknown,
|
|
130
|
-
props?: Record<string, unknown>,
|
|
131
|
-
options?: IslandOptions
|
|
132
|
-
): Island {
|
|
133
|
-
const tagName = component as string;
|
|
134
|
-
const id = this.generateId();
|
|
135
|
-
|
|
136
|
-
// Build attributes
|
|
137
|
-
const attrs = Object.entries(props ?? {})
|
|
138
|
-
.map(([key, value]) => {
|
|
139
|
-
const attrName = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
140
|
-
return `${attrName}="${this.escapeHtml(String(value))}"`;
|
|
141
|
-
})
|
|
142
|
-
.join(' ');
|
|
143
|
-
|
|
144
|
-
const placeholder = `
|
|
145
|
-
<${tagName}
|
|
146
|
-
${attrs}
|
|
147
|
-
data-flight-island="${id}"
|
|
148
|
-
data-flight-hydrate="${options?.hydrate ?? 'load'}"
|
|
149
|
-
></${tagName}>
|
|
150
|
-
`.trim();
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
id,
|
|
154
|
-
component,
|
|
155
|
-
props,
|
|
156
|
-
options: options ?? { hydrate: 'load' },
|
|
157
|
-
placeholder,
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
override getHydrationScript(result: RenderResult): string {
|
|
162
|
-
const data = this.serializeProps(result.hydrationData);
|
|
163
|
-
return `
|
|
164
|
-
<script type="module">
|
|
165
|
-
window.__FLIGHT_DATA__ = ${data};
|
|
166
|
-
window.__FLIGHT_ADAPTER__ = 'stencil';
|
|
167
|
-
// Stencil components self-hydrate via declarative shadow DOM or scripts
|
|
168
|
-
</script>
|
|
169
|
-
`.trim();
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
override getClientEntry(): string {
|
|
173
|
-
return `
|
|
174
|
-
// Stencil Client Entry
|
|
175
|
-
// Web Components hydrate automatically when their scripts are loaded
|
|
176
|
-
|
|
177
|
-
export function hydrate() {
|
|
178
|
-
// Stencil components hydrate automatically
|
|
179
|
-
// Just ensure the component scripts are loaded
|
|
180
|
-
console.log('[Flight/Stencil] Web Components are self-registering');
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export function defineCustomElements(loader) {
|
|
184
|
-
// Load Stencil component definitions
|
|
185
|
-
if (loader && typeof loader.defineCustomElements === 'function') {
|
|
186
|
-
loader.defineCustomElements(window);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
`.trim();
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ============================================================================
|
|
194
|
-
// Factory Function
|
|
195
|
-
// ============================================================================
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Create a Stencil UI adapter.
|
|
199
|
-
*
|
|
200
|
-
* @example
|
|
201
|
-
* ```typescript
|
|
202
|
-
* import { stencil } from '@flightdev/ui/stencil';
|
|
203
|
-
* import { defineUI } from '@flightdev/ui';
|
|
204
|
-
*
|
|
205
|
-
* export default defineUI(stencil({
|
|
206
|
-
* declarativeShadowDom: true,
|
|
207
|
-
* }));
|
|
208
|
-
* ```
|
|
209
|
-
*/
|
|
210
|
-
export function stencil(options?: StencilAdapterOptions): StencilAdapter {
|
|
211
|
-
return new StencilAdapter(options);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export default stencil;
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @flightdev/ui - Alpine.js Adapter (Tier 3)
|
|
3
|
-
*
|
|
4
|
-
* Alpine.js adapter - lightweight reactive HTML enhancement.
|
|
5
|
-
*
|
|
6
|
-
* @module @flightdev/ui/alpine
|
|
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 AlpinePlugin =
|
|
23
|
-
| 'mask'
|
|
24
|
-
| 'intersect'
|
|
25
|
-
| 'persist'
|
|
26
|
-
| 'focus'
|
|
27
|
-
| 'collapse'
|
|
28
|
-
| 'morph'
|
|
29
|
-
| 'sort'
|
|
30
|
-
| 'anchor';
|
|
31
|
-
|
|
32
|
-
export interface AlpineAdapterOptions {
|
|
33
|
-
/** Alpine.js version */
|
|
34
|
-
version?: string;
|
|
35
|
-
|
|
36
|
-
/** Plugins to include */
|
|
37
|
-
plugins?: AlpinePlugin[];
|
|
38
|
-
|
|
39
|
-
/** Use CSP-safe build (no eval) */
|
|
40
|
-
csp?: boolean;
|
|
41
|
-
|
|
42
|
-
/** CDN base URL */
|
|
43
|
-
cdnBase?: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ============================================================================
|
|
47
|
-
// Alpine Adapter
|
|
48
|
-
// ============================================================================
|
|
49
|
-
|
|
50
|
-
export class AlpineAdapter extends BaseUIAdapter {
|
|
51
|
-
readonly id = 'alpine';
|
|
52
|
-
readonly name = 'Alpine.js';
|
|
53
|
-
readonly framework = 'alpine';
|
|
54
|
-
readonly frameworkVersion = '3.14+';
|
|
55
|
-
readonly tier = 'tier-3' as const;
|
|
56
|
-
|
|
57
|
-
override readonly capabilities: AdapterCapabilities = {
|
|
58
|
-
streaming: false,
|
|
59
|
-
partialHydration: false,
|
|
60
|
-
islands: false,
|
|
61
|
-
resumable: false,
|
|
62
|
-
ssg: true,
|
|
63
|
-
csr: true,
|
|
64
|
-
serverComponents: false,
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
constructor(private options: AlpineAdapterOptions = {}) {
|
|
68
|
-
super();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async renderToString(
|
|
72
|
-
component: Component,
|
|
73
|
-
_context?: RenderContext
|
|
74
|
-
): Promise<RenderResult> {
|
|
75
|
-
const startTime = performance.now();
|
|
76
|
-
|
|
77
|
-
// Alpine components are HTML strings with x-data directives
|
|
78
|
-
let html: string;
|
|
79
|
-
|
|
80
|
-
if (typeof component.component === 'function') {
|
|
81
|
-
// Template function
|
|
82
|
-
html = (component.component as (props: unknown) => string)(component.props ?? {});
|
|
83
|
-
} else if (typeof component.component === 'string') {
|
|
84
|
-
// Raw HTML string
|
|
85
|
-
html = component.component;
|
|
86
|
-
} else {
|
|
87
|
-
throw new Error(
|
|
88
|
-
'[Flight/Alpine] Components must be HTML strings or template functions.\n' +
|
|
89
|
-
'Alpine uses declarative HTML with x-data, x-bind, etc.'
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
html,
|
|
95
|
-
hydrationData: component.props,
|
|
96
|
-
timing: this.createTiming(startTime),
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
override getHydrationScript(_result: RenderResult): string {
|
|
101
|
-
const {
|
|
102
|
-
version = '3.14.3',
|
|
103
|
-
plugins = [],
|
|
104
|
-
csp = false,
|
|
105
|
-
cdnBase = 'https://cdn.jsdelivr.net/npm',
|
|
106
|
-
} = this.options;
|
|
107
|
-
|
|
108
|
-
// Build plugin script tags
|
|
109
|
-
const pluginScripts = plugins
|
|
110
|
-
.map((plugin) =>
|
|
111
|
-
`<script defer src="${cdnBase}/@alpinejs/${plugin}@${version}/dist/cdn.min.js"></script>`
|
|
112
|
-
)
|
|
113
|
-
.join('\n');
|
|
114
|
-
|
|
115
|
-
// Main Alpine script (CSP or regular)
|
|
116
|
-
const alpineScript = csp
|
|
117
|
-
? `<script defer src="${cdnBase}/@alpinejs/csp@${version}/dist/cdn.min.js"></script>`
|
|
118
|
-
: `<script defer src="${cdnBase}/alpinejs@${version}/dist/cdn.min.js"></script>`;
|
|
119
|
-
|
|
120
|
-
return `
|
|
121
|
-
<!-- Flight/Alpine.js -->
|
|
122
|
-
${pluginScripts}
|
|
123
|
-
${alpineScript}
|
|
124
|
-
<script>
|
|
125
|
-
window.__FLIGHT_ADAPTER__ = 'alpine';
|
|
126
|
-
</script>
|
|
127
|
-
`.trim();
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
override getClientEntry(): string {
|
|
131
|
-
return `
|
|
132
|
-
// Alpine.js Client Entry
|
|
133
|
-
// Alpine initializes automatically via x-data directives in HTML
|
|
134
|
-
|
|
135
|
-
export function hydrate() {
|
|
136
|
-
// Alpine auto-initializes when loaded
|
|
137
|
-
// No explicit hydration needed
|
|
138
|
-
console.log('[Flight/Alpine] Declarative reactivity active');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function initWithPlugins(plugins = []) {
|
|
142
|
-
// Dynamically load plugins before Alpine
|
|
143
|
-
return Promise.all(
|
|
144
|
-
plugins.map(plugin =>
|
|
145
|
-
import(\`https://cdn.jsdelivr.net/npm/@alpinejs/\${plugin}/dist/cdn.min.js\`)
|
|
146
|
-
)
|
|
147
|
-
).then(() => {
|
|
148
|
-
return import('alpinejs');
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
`.trim();
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// ============================================================================
|
|
156
|
-
// Template Helpers
|
|
157
|
-
// ============================================================================
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Create an x-data component wrapper
|
|
161
|
-
*/
|
|
162
|
-
export function xData(data: Record<string, unknown>, content: string): string {
|
|
163
|
-
const dataStr = JSON.stringify(data).replace(/"/g, "'");
|
|
164
|
-
return `<div x-data="${dataStr}">${content}</div>`;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Create an x-for loop
|
|
169
|
-
* @param expression - The for expression, e.g., 'item in items'
|
|
170
|
-
* @param content - The content to repeat
|
|
171
|
-
*/
|
|
172
|
-
export function xFor(expression: string, content: string): string {
|
|
173
|
-
return `<template x-for="${expression}">${content}</template>`;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Create an x-if conditional
|
|
178
|
-
*/
|
|
179
|
-
export function xIf(condition: string, content: string): string {
|
|
180
|
-
return `<template x-if="${condition}">${content}</template>`;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Create x-bind shorthand
|
|
185
|
-
*/
|
|
186
|
-
export function xBind(attr: string, value: string): string {
|
|
187
|
-
return `:${attr}="${value}"`;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Create x-on shorthand
|
|
192
|
-
*/
|
|
193
|
-
export function xOn(event: string, handler: string): string {
|
|
194
|
-
return `@${event}="${handler}"`;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// ============================================================================
|
|
198
|
-
// Factory Function
|
|
199
|
-
// ============================================================================
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Create an Alpine.js UI adapter.
|
|
203
|
-
*
|
|
204
|
-
* @example
|
|
205
|
-
* ```typescript
|
|
206
|
-
* import { alpine } from '@flightdev/ui/alpine';
|
|
207
|
-
* import { defineUI } from '@flightdev/ui';
|
|
208
|
-
*
|
|
209
|
-
* export default defineUI(alpine({
|
|
210
|
-
* plugins: ['intersect', 'persist'],
|
|
211
|
-
* }));
|
|
212
|
-
* ```
|
|
213
|
-
*/
|
|
214
|
-
export function alpine(options?: AlpineAdapterOptions): AlpineAdapter {
|
|
215
|
-
return new AlpineAdapter(options);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export default alpine;
|
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @flightdev/ui - Hotwire Adapter (Tier 3)
|
|
3
|
-
*
|
|
4
|
-
* Hotwire (Turbo + Stimulus) adapter - Rails-style HTML streaming.
|
|
5
|
-
*
|
|
6
|
-
* @module @flightdev/ui/hotwire
|
|
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 HotwireAdapterOptions {
|
|
23
|
-
/** Enable Turbo Drive (page navigation) */
|
|
24
|
-
turboDrive?: boolean;
|
|
25
|
-
|
|
26
|
-
/** Enable Turbo Frames (partial page updates) */
|
|
27
|
-
turboFrames?: boolean;
|
|
28
|
-
|
|
29
|
-
/** Enable Turbo Streams (server-pushed updates) */
|
|
30
|
-
turboStreams?: boolean;
|
|
31
|
-
|
|
32
|
-
/** Include Stimulus */
|
|
33
|
-
stimulus?: boolean;
|
|
34
|
-
|
|
35
|
-
/** Turbo version */
|
|
36
|
-
turboVersion?: string;
|
|
37
|
-
|
|
38
|
-
/** Stimulus version */
|
|
39
|
-
stimulusVersion?: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ============================================================================
|
|
43
|
-
// Hotwire Adapter
|
|
44
|
-
// ============================================================================
|
|
45
|
-
|
|
46
|
-
export class HotwireAdapter extends BaseUIAdapter {
|
|
47
|
-
readonly id = 'hotwire';
|
|
48
|
-
readonly name = 'Hotwire';
|
|
49
|
-
readonly framework = 'hotwire';
|
|
50
|
-
readonly frameworkVersion = '8+';
|
|
51
|
-
readonly tier = 'tier-3' as const;
|
|
52
|
-
|
|
53
|
-
override readonly capabilities: AdapterCapabilities = {
|
|
54
|
-
streaming: false,
|
|
55
|
-
partialHydration: false,
|
|
56
|
-
islands: false,
|
|
57
|
-
resumable: false,
|
|
58
|
-
ssg: true,
|
|
59
|
-
csr: false, // Hotwire is server-driven
|
|
60
|
-
serverComponents: false,
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
constructor(private options: HotwireAdapterOptions = {}) {
|
|
64
|
-
super();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async renderToString(
|
|
68
|
-
component: Component,
|
|
69
|
-
_context?: RenderContext
|
|
70
|
-
): Promise<RenderResult> {
|
|
71
|
-
const startTime = performance.now();
|
|
72
|
-
|
|
73
|
-
let html: string;
|
|
74
|
-
|
|
75
|
-
if (typeof component.component === 'function') {
|
|
76
|
-
html = (component.component as (props: unknown) => string)(component.props ?? {});
|
|
77
|
-
} else if (typeof component.component === 'string') {
|
|
78
|
-
html = component.component;
|
|
79
|
-
} else {
|
|
80
|
-
throw new Error(
|
|
81
|
-
'[Flight/Hotwire] Components must be HTML strings or template functions.\n' +
|
|
82
|
-
'Use turbo-frame, turbo-stream, and data-controller for interactivity.'
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
html,
|
|
88
|
-
timing: this.createTiming(startTime),
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
override getHydrationScript(_result: RenderResult): string {
|
|
93
|
-
const {
|
|
94
|
-
turboDrive = true,
|
|
95
|
-
turboFrames = true,
|
|
96
|
-
turboStreams = true,
|
|
97
|
-
stimulus = true,
|
|
98
|
-
turboVersion = '8.0.4',
|
|
99
|
-
stimulusVersion = '3.2.2',
|
|
100
|
-
} = this.options;
|
|
101
|
-
|
|
102
|
-
let scripts = `
|
|
103
|
-
<!-- Flight/Hotwire -->
|
|
104
|
-
<script type="module">
|
|
105
|
-
import * as Turbo from 'https://esm.sh/@hotwired/turbo@${turboVersion}';
|
|
106
|
-
window.Turbo = Turbo;
|
|
107
|
-
`;
|
|
108
|
-
|
|
109
|
-
if (!turboDrive) {
|
|
110
|
-
scripts += `Turbo.session.drive = false;\n`;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
scripts += `
|
|
114
|
-
window.__FLIGHT_ADAPTER__ = 'hotwire';
|
|
115
|
-
console.log('[Flight/Hotwire] Turbo initialized');
|
|
116
|
-
</script>
|
|
117
|
-
`;
|
|
118
|
-
|
|
119
|
-
if (stimulus) {
|
|
120
|
-
scripts += `
|
|
121
|
-
<script type="module">
|
|
122
|
-
import { Application } from 'https://esm.sh/@hotwired/stimulus@${stimulusVersion}';
|
|
123
|
-
|
|
124
|
-
window.Stimulus = Application.start();
|
|
125
|
-
|
|
126
|
-
// Auto-register controllers via data-controller-url attribute
|
|
127
|
-
document.querySelectorAll('[data-controller-url]').forEach(async (el) => {
|
|
128
|
-
const url = el.dataset.controllerUrl;
|
|
129
|
-
const name = el.dataset.controller;
|
|
130
|
-
if (url && name) {
|
|
131
|
-
const module = await import(url);
|
|
132
|
-
window.Stimulus.register(name, module.default);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
</script>
|
|
136
|
-
`;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return scripts.trim();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
override getClientEntry(): string {
|
|
143
|
-
return `
|
|
144
|
-
import * as Turbo from '@hotwired/turbo';
|
|
145
|
-
import { Application } from '@hotwired/stimulus';
|
|
146
|
-
|
|
147
|
-
// Initialize Turbo
|
|
148
|
-
export function initTurbo(options = {}) {
|
|
149
|
-
if (options.drive === false) {
|
|
150
|
-
Turbo.session.drive = false;
|
|
151
|
-
}
|
|
152
|
-
return Turbo;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Initialize Stimulus
|
|
156
|
-
export function initStimulus() {
|
|
157
|
-
const application = Application.start();
|
|
158
|
-
window.Stimulus = application;
|
|
159
|
-
return application;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Auto-register Stimulus controllers
|
|
163
|
-
export function registerControllers(application, controllers) {
|
|
164
|
-
for (const [name, controller] of Object.entries(controllers)) {
|
|
165
|
-
application.register(name, controller);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Default hydration
|
|
170
|
-
export function hydrate() {
|
|
171
|
-
initTurbo();
|
|
172
|
-
initStimulus();
|
|
173
|
-
console.log('[Flight/Hotwire] Application ready');
|
|
174
|
-
}
|
|
175
|
-
`.trim();
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// ============================================================================
|
|
180
|
-
// Template Helpers
|
|
181
|
-
// ============================================================================
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Create a Turbo Frame
|
|
185
|
-
*/
|
|
186
|
-
export function turboFrame(id: string, content: string, options: {
|
|
187
|
-
src?: string;
|
|
188
|
-
loading?: 'eager' | 'lazy';
|
|
189
|
-
target?: string;
|
|
190
|
-
} = {}): string {
|
|
191
|
-
const attrs = [`id="${id}"`];
|
|
192
|
-
if (options.src) attrs.push(`src="${options.src}"`);
|
|
193
|
-
if (options.loading) attrs.push(`loading="${options.loading}"`);
|
|
194
|
-
if (options.target) attrs.push(`target="${options.target}"`);
|
|
195
|
-
|
|
196
|
-
return `<turbo-frame ${attrs.join(' ')}>${content}</turbo-frame>`;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Create a Turbo Stream action
|
|
201
|
-
*/
|
|
202
|
-
export function turboStream(
|
|
203
|
-
action: 'append' | 'prepend' | 'replace' | 'update' | 'remove' | 'before' | 'after',
|
|
204
|
-
target: string,
|
|
205
|
-
content?: string
|
|
206
|
-
): string {
|
|
207
|
-
if (action === 'remove') {
|
|
208
|
-
return `<turbo-stream action="remove" target="${target}"></turbo-stream>`;
|
|
209
|
-
}
|
|
210
|
-
return `
|
|
211
|
-
<turbo-stream action="${action}" target="${target}">
|
|
212
|
-
<template>${content ?? ''}</template>
|
|
213
|
-
</turbo-stream>
|
|
214
|
-
`.trim();
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Create a Stimulus controller element
|
|
219
|
-
*/
|
|
220
|
-
export function stimulusController(
|
|
221
|
-
controller: string,
|
|
222
|
-
content: string,
|
|
223
|
-
values: Record<string, unknown> = {}
|
|
224
|
-
): string {
|
|
225
|
-
const valueAttrs = Object.entries(values)
|
|
226
|
-
.map(([key, value]) => `data-${controller}-${key}-value="${value}"`)
|
|
227
|
-
.join(' ');
|
|
228
|
-
|
|
229
|
-
return `<div data-controller="${controller}" ${valueAttrs}>${content}</div>`;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// ============================================================================
|
|
233
|
-
// Factory Function
|
|
234
|
-
// ============================================================================
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Create a Hotwire UI adapter.
|
|
238
|
-
*
|
|
239
|
-
* @example
|
|
240
|
-
* ```typescript
|
|
241
|
-
* import { hotwire } from '@flightdev/ui/hotwire';
|
|
242
|
-
* import { defineUI } from '@flightdev/ui';
|
|
243
|
-
*
|
|
244
|
-
* export default defineUI(hotwire({
|
|
245
|
-
* turboStreams: true,
|
|
246
|
-
* stimulus: true,
|
|
247
|
-
* }));
|
|
248
|
-
* ```
|
|
249
|
-
*/
|
|
250
|
-
export function hotwire(options?: HotwireAdapterOptions): HotwireAdapter {
|
|
251
|
-
return new HotwireAdapter(options);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export default hotwire;
|