snice 1.14.0 → 1.14.1

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/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v1.13.11
2
+ * snice v1.14.0
3
3
  * Imperative TypeScript framework for building vanilla web components with decorators, routing, and controllers. No virtual DOM, no build complexity.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v1.13.11
2
+ * snice v1.14.0
3
3
  * Imperative TypeScript framework for building vanilla web components with decorators, routing, and controllers. No virtual DOM, no build complexity.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
package/dist/index.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v1.13.11
2
+ * snice v1.14.0
3
3
  * Imperative TypeScript framework for building vanilla web components with decorators, routing, and controllers. No virtual DOM, no build complexity.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v1.13.11
2
+ * snice v1.14.0
3
3
  * Imperative TypeScript framework for building vanilla web components with decorators, routing, and controllers. No virtual DOM, no build complexity.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v1.13.11
2
+ * snice v1.14.0
3
3
  * Imperative TypeScript framework for building vanilla web components with decorators, routing, and controllers. No virtual DOM, no build complexity.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v1.13.11
2
+ * snice v1.14.0
3
3
  * Imperative TypeScript framework for building vanilla web components with decorators, routing, and controllers. No virtual DOM, no build complexity.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
package/dist/symbols.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v1.13.11
2
+ * snice v1.14.0
3
3
  * Imperative TypeScript framework for building vanilla web components with decorators, routing, and controllers. No virtual DOM, no build complexity.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v1.13.11
2
+ * snice v1.14.0
3
3
  * Imperative TypeScript framework for building vanilla web components with decorators, routing, and controllers. No virtual DOM, no build complexity.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v1.13.11
2
+ * snice v1.14.0
3
3
  * Imperative TypeScript framework for building vanilla web components with decorators, routing, and controllers. No virtual DOM, no build complexity.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v1.13.11
2
+ * snice v1.14.0
3
3
  * Imperative TypeScript framework for building vanilla web components with decorators, routing, and controllers. No virtual DOM, no build complexity.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snice",
3
- "version": "1.14.0",
3
+ "version": "1.14.1",
4
4
  "type": "module",
5
5
  "description": "Imperative TypeScript framework for building vanilla web components with decorators, routing, and controllers. No virtual DOM, no build complexity.",
6
6
  "main": "dist/index.cjs",
@@ -23,14 +23,14 @@
23
23
  "import": "./dist/transitions.esm.js",
24
24
  "require": "./dist/transitions.cjs",
25
25
  "types": "./dist/types/transitions.d.ts"
26
- }
26
+ },
27
+ "./components/*": "./components/*"
27
28
  },
28
29
  "bin": {
29
30
  "snice": "./bin/snice.js"
30
31
  },
31
32
  "files": [
32
33
  "dist",
33
- "src",
34
34
  "components",
35
35
  "bin",
36
36
  "README.md",
package/src/controller.ts DELETED
@@ -1,347 +0,0 @@
1
- import { setupEventHandlers, cleanupEventHandlers } from './events';
2
- import { setupObservers, cleanupObservers } from './observe';
3
- import { setupResponseHandlers, cleanupResponseHandlers } from './request-response';
4
- import { IS_CONTROLLER_CLASS, IS_CONTROLLER_INSTANCE, CONTROLLER_KEY, CONTROLLER_NAME_KEY, CONTROLLER_ID, CONTROLLER_OPERATIONS, NATIVE_CONTROLLER, IS_ELEMENT_CLASS, ROUTER_CONTEXT } from './symbols';
5
- import { snice } from './global';
6
-
7
- type Maybe<T> = T | null | undefined;
8
-
9
- export interface IController<T extends HTMLElement = HTMLElement> {
10
- element: Maybe<T>;
11
- attach(element: T): void | Promise<void>;
12
- detach(element: T): void | Promise<void>;
13
- }
14
-
15
- export type ControllerClass<T extends HTMLElement = HTMLElement> = new() => IController<T>;
16
-
17
- // Controller-scoped cleanup registry
18
- class ControllerScope {
19
- private cleanupFns: Map<string, Function> = new Map();
20
- private pendingOperations: Set<Promise<void>> = new Set();
21
-
22
- register(key: string, cleanup: Function): void {
23
- this.cleanupFns.set(key, cleanup);
24
- }
25
-
26
- unregister(key: string): void {
27
- this.cleanupFns.delete(key);
28
- }
29
-
30
- async cleanup(): Promise<void> {
31
- // Wait for all pending operations
32
- await Promise.all(this.pendingOperations);
33
-
34
- // Run all cleanup functions
35
- for (const cleanup of this.cleanupFns.values()) {
36
- try {
37
- await cleanup();
38
- } catch (error) {
39
- console.error('Error during cleanup:', error);
40
- }
41
- }
42
- this.cleanupFns.clear();
43
- }
44
-
45
- async runOperation<T>(operation: () => Promise<T>): Promise<T> {
46
- const promise = operation();
47
- const voidPromise = promise.then(() => {}, () => {});
48
- this.pendingOperations.add(voidPromise);
49
-
50
- try {
51
- const result = await promise;
52
- this.pendingOperations.delete(voidPromise);
53
- return result;
54
- } catch (error) {
55
- this.pendingOperations.delete(voidPromise);
56
- throw error;
57
- }
58
- }
59
- }
60
-
61
- /**
62
- * Decorator to register a controller class with a name
63
- * @param name The name to register the controller under
64
- */
65
- export function controller(name: string) {
66
- return function <T extends ControllerClass>(constructor: T) {
67
- snice.controllerRegistry.set(name, constructor);
68
- // Mark as controller class for channel decorator detection
69
- (constructor.prototype as any)[IS_CONTROLLER_CLASS] = true;
70
- return constructor;
71
- };
72
- }
73
-
74
- /**
75
- * Attaches a controller to an element
76
- * @param element The element to attach the controller to
77
- * @param controllerName The name of the controller to attach
78
- */
79
- export async function attachController(element: HTMLElement, controllerName: string): Promise<void> {
80
- const existingController = (element as any)[CONTROLLER_KEY] as IController | undefined;
81
- const existingName = (element as any)[CONTROLLER_NAME_KEY] as string | undefined;
82
-
83
- // For native elements, check if this is actually the desired controller
84
- const nativeController = (element as any)[NATIVE_CONTROLLER];
85
- if (nativeController !== undefined && nativeController !== controllerName) {
86
- // This attachment is outdated, skip it
87
- return;
88
- }
89
-
90
- if (existingName === controllerName && existingController) {
91
- // Already attached and controller exists
92
- return;
93
- }
94
-
95
- // If there's an existing controller, detach it first
96
- if (existingController) {
97
- await detachController(element);
98
- }
99
-
100
- const ControllerClass = snice.controllerRegistry.get(controllerName);
101
- if (!ControllerClass) {
102
- throw new Error(`Controller "${controllerName}" not found in registry`);
103
- }
104
-
105
- // Create controller instance with unique ID and scope
106
- const controllerInstance = new ControllerClass();
107
- snice.controllerIdCounter += 1;
108
- const controllerId = snice.controllerIdCounter;
109
- const scope = new ControllerScope();
110
-
111
- // Mark this as a controller instance
112
- (controllerInstance as any)[IS_CONTROLLER_INSTANCE] = true;
113
- (controllerInstance as any)[CONTROLLER_ID] = controllerId;
114
- controllerInstance.element = element;
115
-
116
- // Pass router context from element to controller if it exists
117
- const routerContext = (element as any)[ROUTER_CONTEXT];
118
- if (routerContext !== undefined) {
119
- (controllerInstance as any)[ROUTER_CONTEXT] = routerContext;
120
- }
121
-
122
- // Store references
123
- (element as any)[CONTROLLER_KEY] = controllerInstance;
124
- (element as any)[CONTROLLER_NAME_KEY] = controllerName;
125
- (element as any)[CONTROLLER_OPERATIONS] = scope;
126
-
127
- // Wait for element to be ready (required)
128
- await (element as any).ready;
129
-
130
- // Run attach in the controller's scope
131
- await scope.runOperation(async () => {
132
- await controllerInstance.attach(element);
133
- });
134
-
135
- // Setup @on event handlers for controller
136
- setupEventHandlers(controllerInstance, element);
137
-
138
- // Setup @observe observers for controller
139
- setupObservers(controllerInstance, element);
140
-
141
- // Setup @channel handlers for controller
142
- setupResponseHandlers(controllerInstance, element);
143
-
144
- element.dispatchEvent(new CustomEvent('@snice/controller-attached', {
145
- detail: { name: controllerName, controller: controllerInstance }
146
- }));
147
- }
148
-
149
- /**
150
- * Detaches a controller from an element
151
- * @param element The element to detach the controller from
152
- */
153
- export async function detachController(element: HTMLElement): Promise<void> {
154
- const controllerInstance = (element as any)[CONTROLLER_KEY] as IController | undefined;
155
- const controllerName = (element as any)[CONTROLLER_NAME_KEY] as string | undefined;
156
- const scope = (element as any)[CONTROLLER_OPERATIONS] as ControllerScope | undefined;
157
-
158
- if (!controllerInstance) {
159
- return;
160
- }
161
-
162
- // Run detach in the controller's scope
163
- if (scope) {
164
- await scope.runOperation(async () => {
165
- await controllerInstance.detach(element);
166
- });
167
- } else {
168
- await controllerInstance.detach(element);
169
- }
170
-
171
- controllerInstance.element = null;
172
-
173
- // Cleanup @on event handlers for controller
174
- cleanupEventHandlers(controllerInstance);
175
-
176
- // Cleanup @observe observers for controller
177
- cleanupObservers(controllerInstance);
178
-
179
- // Cleanup @channel handlers for controller
180
- cleanupResponseHandlers(controllerInstance);
181
-
182
- // Cleanup the controller scope
183
- if (scope) {
184
- await scope.cleanup();
185
- }
186
-
187
- // Clean up router context reference
188
- delete (controllerInstance as any)[ROUTER_CONTEXT];
189
-
190
- delete (element as any)[CONTROLLER_KEY];
191
- delete (element as any)[CONTROLLER_NAME_KEY];
192
- delete (element as any)[CONTROLLER_OPERATIONS];
193
-
194
- element.dispatchEvent(new CustomEvent('@snice/controller-detached', {
195
- detail: { name: controllerName, controller: controllerInstance }
196
- }));
197
- }
198
-
199
- /**
200
- * Gets the controller instance attached to an element
201
- * @param element The element to get the controller from
202
- * @returns The controller instance or undefined
203
- */
204
- export function getController<T extends IController = IController>(element: HTMLElement): T | undefined {
205
- return (element as any)[CONTROLLER_KEY] as T | undefined;
206
- }
207
-
208
- /**
209
- * Gets the controller scope for an element
210
- * @param element The element to get the scope from
211
- * @returns The controller scope or undefined
212
- */
213
- export function getControllerScope(element: HTMLElement): ControllerScope | undefined {
214
- return (element as any)[CONTROLLER_OPERATIONS] as ControllerScope | undefined;
215
- }
216
-
217
- /**
218
- * Enable controller support for native HTML elements
219
- * This sets up a MutationObserver to watch for controller attributes
220
- * on non-custom elements (elements without hyphens in their tag names)
221
- */
222
- export function useNativeElementControllers() {
223
- // Return if already initialized
224
- if ((globalThis as any).sniceNativeControllersInitialized) {
225
- return;
226
- }
227
- (globalThis as any).sniceNativeControllersInitialized = true;
228
-
229
- // Process elements that already have controller attribute
230
- function processElement(element: Element) {
231
- if (!(element instanceof HTMLElement)) return;
232
-
233
- // Skip custom elements (they handle controllers themselves)
234
- if (element.tagName.includes('-')) return;
235
-
236
- // Skip elements that are @element decorated (they have their own controller handling)
237
- if ((element as any)[IS_ELEMENT_CLASS]) return;
238
-
239
- const controllerName = element.getAttribute('controller');
240
- const currentControllerName = (element as any)[NATIVE_CONTROLLER];
241
-
242
- if (controllerName && controllerName !== currentControllerName) {
243
- // Controller added or changed
244
- (element as any)[NATIVE_CONTROLLER] = controllerName;
245
-
246
- // For non-custom elements, we need to add the ready promise
247
- if (!(element as any).ready) {
248
- (element as any).ready = Promise.resolve();
249
- }
250
-
251
- // Detach old controller if exists (don't await - let it run async)
252
- if (currentControllerName) {
253
- detachController(element as HTMLElement).catch(error => {
254
- console.error(`Failed to detach old controller from native element:`, error);
255
- });
256
- }
257
-
258
- // Attach the new controller
259
- attachController(element as HTMLElement, controllerName).catch(error => {
260
- console.error(`Failed to attach controller "${controllerName}" to native element:`, error);
261
- });
262
- } else if (!controllerName && currentControllerName) {
263
- // Controller was removed
264
- delete (element as any)[NATIVE_CONTROLLER];
265
- // Clear the controller name immediately to allow re-attachment
266
- delete (element as any)[CONTROLLER_NAME_KEY];
267
- detachController(element as HTMLElement).catch(error => {
268
- console.error(`Failed to detach controller from native element:`, error);
269
- });
270
- }
271
- }
272
-
273
- // Set up MutationObserver to watch for controller attributes
274
- const observer = new MutationObserver((mutations) => {
275
- for (const mutation of mutations) {
276
- if (mutation.type === 'attributes' && mutation.attributeName === 'controller') {
277
- processElement(mutation.target as Element);
278
- } else if (mutation.type === 'childList') {
279
- // Process added nodes
280
- mutation.addedNodes.forEach(node => {
281
- if (node instanceof HTMLElement) {
282
- // Process the node itself
283
- processElement(node);
284
- // Process all descendants with controller attribute
285
- node.querySelectorAll('[controller]:not([class*="-"])').forEach(processElement);
286
- }
287
- });
288
- }
289
- }
290
- });
291
-
292
- // Start observing when DOM is ready
293
- if (document.readyState === 'loading') {
294
- document.addEventListener('DOMContentLoaded', () => {
295
- // Process existing elements (excluding custom elements)
296
- document.querySelectorAll('[controller]:not([class*="-"])').forEach(processElement);
297
-
298
- // Start observing
299
- observer.observe(document.body, {
300
- attributes: true,
301
- attributeFilter: ['controller'],
302
- childList: true,
303
- subtree: true
304
- });
305
- });
306
- } else {
307
- // DOM already loaded
308
- document.querySelectorAll('[controller]:not([class*="-"])').forEach(processElement);
309
-
310
- observer.observe(document.body, {
311
- attributes: true,
312
- attributeFilter: ['controller'],
313
- childList: true,
314
- subtree: true
315
- });
316
- }
317
-
318
- // Store observer reference for cleanup if needed
319
- (globalThis as any).sniceNativeControllerObserver = observer;
320
- }
321
-
322
- /**
323
- * Stop watching for native element controllers
324
- */
325
- export function cleanupNativeElementControllers() {
326
- const observer = (globalThis as any).sniceNativeControllerObserver;
327
- if (observer) {
328
- observer.disconnect();
329
- delete (globalThis as any).sniceNativeControllerObserver;
330
- delete (globalThis as any).sniceNativeControllersInitialized;
331
- }
332
- }
333
-
334
- /**
335
- * Registers a cleanup function for the current controller
336
- * @param controller The controller instance
337
- * @param key A unique key for this cleanup function
338
- * @param cleanup The cleanup function to register
339
- */
340
- export function registerControllerCleanup(controller: IController, key: string, cleanup: Function): void {
341
- if (!controller.element) return;
342
-
343
- const scope = getControllerScope(controller.element as HTMLElement);
344
- if (scope) {
345
- scope.register(key, cleanup);
346
- }
347
- }