@flightdev/ui 2.0.0 → 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.
Files changed (120) hide show
  1. package/README.md +285 -70
  2. package/dist/{chunk-XTDK7ME5.js → chunk-S4DTUQII.js} +246 -19
  3. package/dist/chunk-S4DTUQII.js.map +1 -0
  4. package/dist/core/index.d.ts +423 -3
  5. package/dist/core/index.js +23 -2
  6. package/dist/core/index.js.map +1 -0
  7. package/dist/index.d.ts +2 -3
  8. package/dist/index.js +29 -5
  9. package/dist/index.js.map +1 -0
  10. package/package.json +11 -181
  11. package/.turbo/turbo-build.log +0 -81
  12. package/.turbo/turbo-lint.log +0 -40
  13. package/.turbo/turbo-typecheck.log +0 -4
  14. package/TESTING.md +0 -124
  15. package/dist/adapter-MMD-iHNx.d.ts +0 -424
  16. package/dist/adapters/tier-1/angular.d.ts +0 -60
  17. package/dist/adapters/tier-1/angular.js +0 -2
  18. package/dist/adapters/tier-1/index.d.ts +0 -7
  19. package/dist/adapters/tier-1/index.js +0 -7
  20. package/dist/adapters/tier-1/qwik.d.ts +0 -55
  21. package/dist/adapters/tier-1/qwik.js +0 -2
  22. package/dist/adapters/tier-1/react.d.ts +0 -67
  23. package/dist/adapters/tier-1/react.js +0 -2
  24. package/dist/adapters/tier-1/solid.d.ts +0 -45
  25. package/dist/adapters/tier-1/solid.js +0 -2
  26. package/dist/adapters/tier-1/svelte.d.ts +0 -48
  27. package/dist/adapters/tier-1/svelte.js +0 -2
  28. package/dist/adapters/tier-1/vue.d.ts +0 -47
  29. package/dist/adapters/tier-1/vue.js +0 -2
  30. package/dist/adapters/tier-2/index.d.ts +0 -7
  31. package/dist/adapters/tier-2/index.js +0 -7
  32. package/dist/adapters/tier-2/inferno.d.ts +0 -31
  33. package/dist/adapters/tier-2/inferno.js +0 -2
  34. package/dist/adapters/tier-2/lit.d.ts +0 -34
  35. package/dist/adapters/tier-2/lit.js +0 -2
  36. package/dist/adapters/tier-2/marko.d.ts +0 -59
  37. package/dist/adapters/tier-2/marko.js +0 -2
  38. package/dist/adapters/tier-2/mithril.d.ts +0 -31
  39. package/dist/adapters/tier-2/mithril.js +0 -2
  40. package/dist/adapters/tier-2/preact.d.ts +0 -33
  41. package/dist/adapters/tier-2/preact.js +0 -2
  42. package/dist/adapters/tier-2/stencil.d.ts +0 -52
  43. package/dist/adapters/tier-2/stencil.js +0 -2
  44. package/dist/adapters/tier-3/alpine.d.ts +0 -73
  45. package/dist/adapters/tier-3/alpine.js +0 -2
  46. package/dist/adapters/tier-3/hotwire.d.ts +0 -71
  47. package/dist/adapters/tier-3/hotwire.js +0 -2
  48. package/dist/adapters/tier-3/htmx.d.ts +0 -88
  49. package/dist/adapters/tier-3/htmx.js +0 -2
  50. package/dist/adapters/tier-3/index.d.ts +0 -7
  51. package/dist/adapters/tier-3/index.js +0 -7
  52. package/dist/adapters/tier-3/petite-vue.d.ts +0 -56
  53. package/dist/adapters/tier-3/petite-vue.js +0 -2
  54. package/dist/adapters/tier-3/stimulus.d.ts +0 -63
  55. package/dist/adapters/tier-3/stimulus.js +0 -2
  56. package/dist/adapters/tier-3/vanilla.d.ts +0 -63
  57. package/dist/adapters/tier-3/vanilla.js +0 -2
  58. package/dist/chunk-2SNQ6PTM.js +0 -217
  59. package/dist/chunk-3D4XMIZI.js +0 -136
  60. package/dist/chunk-3HU6GSQ4.js +0 -125
  61. package/dist/chunk-4PZDNFL7.js +0 -148
  62. package/dist/chunk-5IBLFTYL.js +0 -114
  63. package/dist/chunk-64JZJ7OK.js +0 -142
  64. package/dist/chunk-7ZJI3QU2.js +0 -132
  65. package/dist/chunk-CE4FJHQJ.js +0 -133
  66. package/dist/chunk-DTCAUBH5.js +0 -87
  67. package/dist/chunk-NTASPOHG.js +0 -106
  68. package/dist/chunk-OI2AMQLG.js +0 -152
  69. package/dist/chunk-Q7HUE44H.js +0 -106
  70. package/dist/chunk-QH3LOWXU.js +0 -155
  71. package/dist/chunk-QIVAK6BH.js +0 -103
  72. package/dist/chunk-V34XPVGK.js +0 -103
  73. package/dist/chunk-VK7ZPMO7.js +0 -221
  74. package/dist/chunk-X6CNUW6T.js +0 -136
  75. package/dist/chunk-YFGSHW5S.js +0 -121
  76. package/dist/chunk-ZAJVSE7J.js +0 -90
  77. package/docs/ADAPTERS.md +0 -946
  78. package/docs/PATTERNS.md +0 -836
  79. package/src/adapters/tier-1/angular.ts +0 -223
  80. package/src/adapters/tier-1/index.ts +0 -12
  81. package/src/adapters/tier-1/qwik.ts +0 -177
  82. package/src/adapters/tier-1/react.ts +0 -330
  83. package/src/adapters/tier-1/solid.ts +0 -222
  84. package/src/adapters/tier-1/svelte.ts +0 -211
  85. package/src/adapters/tier-1/vue.ts +0 -234
  86. package/src/adapters/tier-2/index.ts +0 -12
  87. package/src/adapters/tier-2/inferno.ts +0 -149
  88. package/src/adapters/tier-2/lit.ts +0 -191
  89. package/src/adapters/tier-2/marko.ts +0 -199
  90. package/src/adapters/tier-2/mithril.ts +0 -152
  91. package/src/adapters/tier-2/preact.ts +0 -133
  92. package/src/adapters/tier-2/stencil.ts +0 -214
  93. package/src/adapters/tier-3/alpine.ts +0 -218
  94. package/src/adapters/tier-3/hotwire.ts +0 -254
  95. package/src/adapters/tier-3/htmx.ts +0 -263
  96. package/src/adapters/tier-3/index.ts +0 -12
  97. package/src/adapters/tier-3/petite-vue.ts +0 -163
  98. package/src/adapters/tier-3/stimulus.ts +0 -233
  99. package/src/adapters/tier-3/vanilla.ts +0 -252
  100. package/src/ambient.d.ts +0 -310
  101. package/src/core/adapter.ts +0 -366
  102. package/src/core/index.ts +0 -56
  103. package/src/core/registry.ts +0 -518
  104. package/src/core/types.ts +0 -461
  105. package/src/htmx.ts +0 -134
  106. package/src/index.ts +0 -263
  107. package/test/__mocks__/stencil-core.ts +0 -19
  108. package/test/__mocks__/stencil-hydrate.ts +0 -15
  109. package/test/adapters/tier-1.test.ts +0 -206
  110. package/test/adapters/tier-2.test.ts +0 -175
  111. package/test/adapters/tier-3.test.ts +0 -284
  112. package/test/contracts/adapter.contract.ts +0 -293
  113. package/test/core/core.test.ts +0 -310
  114. package/test/errors/error-handling.test.ts +0 -454
  115. package/test/integration/htmx.integration.test.ts +0 -246
  116. package/test/integration/react.integration.test.ts +0 -271
  117. package/test/integration/registry.integration.test.ts +0 -308
  118. package/tsconfig.json +0 -22
  119. package/tsup.config.ts +0 -93
  120. 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;