structured-fw 1.0.7 → 1.0.9

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 (63) hide show
  1. package/README.md +50 -2
  2. package/build/system/EventEmitter.d.ts +4 -4
  3. package/build/system/EventEmitter.js +3 -3
  4. package/build/system/RequestHandler.d.ts +1 -0
  5. package/build/system/RequestHandler.js +1 -0
  6. package/build/system/RequestMethod.d.ts +1 -0
  7. package/build/system/RequestMethod.js +1 -0
  8. package/build/system/Types.d.ts +10 -175
  9. package/build/system/Types.js +10 -1
  10. package/build/system/Util.d.ts +3 -2
  11. package/build/system/Util.js +18 -2
  12. package/build/system/client/ClientComponent.d.ts +2 -1
  13. package/build/system/client/ClientComponent.js +94 -29
  14. package/build/system/client/DataStore.d.ts +1 -1
  15. package/build/system/client/DataStoreView.d.ts +1 -1
  16. package/build/system/client/Net.d.ts +2 -1
  17. package/build/system/client/NetRequest.d.ts +1 -1
  18. package/build/system/request.d.ts +1 -0
  19. package/build/system/request.js +1 -0
  20. package/build/system/server/Application.d.ts +4 -1
  21. package/build/system/server/Application.js +1 -1
  22. package/build/system/server/Component.d.ts +2 -1
  23. package/build/system/server/Component.js +11 -8
  24. package/build/system/server/Components.d.ts +2 -1
  25. package/build/system/server/Components.js +7 -7
  26. package/build/system/server/Cookies.d.ts +1 -1
  27. package/build/system/server/Document.d.ts +5 -3
  28. package/build/system/server/Document.js +3 -3
  29. package/build/system/server/DocumentHead.d.ts +1 -1
  30. package/build/system/server/FormValidation.d.ts +2 -1
  31. package/build/system/server/Handlebars.d.ts +1 -1
  32. package/build/system/server/Layout.d.ts +4 -2
  33. package/build/system/server/Layout.js +3 -1
  34. package/build/system/server/Request.d.ts +2 -1
  35. package/build/system/server/Request.js +4 -3
  36. package/build/system/server/Session.d.ts +2 -1
  37. package/build/system/server/dom/DOMNode.d.ts +1 -0
  38. package/build/system/server/dom/DOMNode.js +9 -3
  39. package/build/system/server/dom/HTMLParser.d.ts +1 -0
  40. package/build/system/server/dom/HTMLParser.js +31 -8
  41. package/build/system/types/application.types.d.ts +1 -0
  42. package/build/system/types/application.types.js +1 -0
  43. package/build/system/types/component.types.d.ts +59 -0
  44. package/build/system/types/component.types.js +1 -0
  45. package/build/system/types/document.types.d.ts +5 -0
  46. package/build/system/types/document.types.js +1 -0
  47. package/build/system/types/eventEmitter.types.d.ts +1 -0
  48. package/build/system/types/eventEmitter.types.js +1 -0
  49. package/build/system/types/general.types.d.ts +1 -0
  50. package/build/system/types/general.types.js +1 -0
  51. package/build/system/types/request.d.ts +57 -0
  52. package/build/system/types/request.js +1 -0
  53. package/build/system/types/request.types.d.ts +58 -0
  54. package/build/system/types/request.types.js +1 -0
  55. package/build/system/types/session.types.d.ts +6 -0
  56. package/build/system/types/session.types.js +1 -0
  57. package/build/system/types/store.types.d.ts +1 -0
  58. package/build/system/types/store.types.js +1 -0
  59. package/build/system/types/structured.types.d.ts +32 -0
  60. package/build/system/types/structured.types.js +1 -0
  61. package/build/system/types/validation.types.d.ts +18 -0
  62. package/build/system/types/validation.types.js +1 -0
  63. package/package.json +3 -2
package/README.md CHANGED
@@ -275,6 +275,9 @@ type RequestContext<Body extends LooseObject | undefined = LooseObject> = {
275
275
  // true if x-requested-with header is received and it equals 'xmlhttprequest'
276
276
  isAjax: boolean,
277
277
 
278
+ // time when request was received (unix timestamp in milliseconds)
279
+ timeStart: number,
280
+
278
281
  // URL GET arguments
279
282
  getArgs: PostedDataDecoded,
280
283
 
@@ -689,6 +692,51 @@ then in ComponentName.html:
689
692
  <div data-if="showDiv()"></div>
690
693
  ```
691
694
 
695
+ ### Models
696
+ Every component client side part has it's own data store accessed by this.store. That is the primary way of storing data for your components client side, because it will survive on redraw and you can subscribe to data changes in the store using `this.store.onChange`.
697
+
698
+ That being said, we need an easy way to use input fields to set values in the store, as that's often what we do when we make web apps.
699
+
700
+ You can, of course, bind an even listener to the input and set the store value, that's quite easy, but we can accomplis this using `data-model` attribute.
701
+
702
+ You can add data-model to any HTMLInput within your component, and it will automatically update the store on input value change.
703
+
704
+ For example:
705
+
706
+ Direct key:\
707
+ `<input type="text" data-model="name">`
708
+
709
+ Direct key access:\
710
+ `this.store.get<string>('name')`
711
+ `// returns string`
712
+
713
+ Nested keys:\
714
+ `<input type="text" data-model="user[name]">`
715
+
716
+ Nested key access:\
717
+ `this.store.get<LooseObject>('user')`
718
+ `// returns { name: string }`
719
+
720
+
721
+ You can nest the keys to any depth, or even make the value an array member if you end the key with `[]`, for example:
722
+ ```
723
+ <input type="text" data-model="user[hobbies][]">
724
+ this.store.get<LooseObject>('user')
725
+ // returns { user: { hobbies: Array<string> } }
726
+ ```
727
+
728
+ You can use two modifier attributes with `data-model`:
729
+ - `data-type`
730
+ - `data-nullable`
731
+
732
+ `data-type` - cast value to given type. Can be one of number | boolean | string, string has no effect as HTMLInput values are already a string by default.\
733
+ If number: if input is empty or value casts to `NaN` then `0` (unless `data-nullable` in which case `null`), othrwise the casted number (uses parseFloat so it works with decimal numbers)\
734
+ If boolean: `"1"` and `"true"` casted to `true`, otherwise `false`\
735
+ If string no type casting is attempted.
736
+
737
+ `data-nullable` - value of this attribute is unused, as long as the attribute is present on the input, empty values will be casted to `null`. Can be used in conjunction with `data-type`
738
+
739
+
692
740
  ### Layout
693
741
  Prior to version 0.8.7:
694
742
 
@@ -721,9 +769,9 @@ Version 0.8.7 introduced the `Layout` class, which allows accomplishing the abov
721
769
  <template></template>
722
770
  ...
723
771
  ```
724
- 2) `/index.ts` (`app` is an instance of `Application`)
772
+ 2) `/index.ts` (`app` is an instance of `Application`), 3rd argument is optional BCP 47 language tag
725
773
  ```
726
- export const layout = new Layout(app, 'layout');
774
+ export const layout = new Layout(app, 'layout', 'en');
727
775
  ```
728
776
  3) `/app/routes/Test.ts`
729
777
  ```
@@ -1,7 +1,7 @@
1
- import { EventEmitterCallback } from "./Types.js";
1
+ import { EventEmitterCallback } from './types/eventEmitter.types.js';
2
2
  export declare class EventEmitter<T extends Record<string, any> = Record<string, any>> {
3
- protected listeners: Partial<Record<keyof T, Array<EventEmitterCallback<any>>>>;
4
- on<K extends keyof T>(eventName: K, callback: EventEmitterCallback<T[K]>): void;
5
- emit(eventName: keyof T, payload?: any): void;
3
+ protected listeners: Partial<Record<Extract<keyof T, string>, Array<EventEmitterCallback<any>>>>;
4
+ on<K extends Extract<keyof T, string>>(eventName: K, callback: EventEmitterCallback<T[K]>): void;
5
+ emit(eventName: Extract<keyof T, string>, payload?: any): void;
6
6
  off(eventName: keyof T, callback: EventEmitterCallback<any>): void;
7
7
  }
@@ -9,9 +9,9 @@ export class EventEmitter {
9
9
  this.listeners[eventName].push(callback);
10
10
  }
11
11
  emit(eventName, payload) {
12
- if (Array.isArray(this.listeners[eventName])) {
13
- this.listeners[eventName].forEach((callback) => {
14
- callback(payload);
12
+ if (Array.isArray(this.listeners[eventName]) || Array.isArray(this.listeners['*'])) {
13
+ (this.listeners[eventName] || []).concat(this.listeners['*'] || []).forEach((callback) => {
14
+ callback(payload, eventName);
15
15
  });
16
16
  }
17
17
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export type RequestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
@@ -0,0 +1 @@
1
+ export {};
@@ -1,175 +1,10 @@
1
- import { IncomingMessage, ServerResponse } from "node:http";
2
- import { Application } from "./server/Application.js";
3
- import { symbolArrays } from "./Symbols.js";
4
- import { Net } from './client/Net.js';
5
- import { ClientComponent } from './client/ClientComponent.js';
6
- import { Component } from "./server/Component.js";
7
- export type StructuredConfig = {
8
- readonly envPrefix?: string;
9
- readonly autoInit: boolean;
10
- url: {
11
- removeTrailingSlash: boolean;
12
- componentRender: false | string;
13
- isAsset: (url: string) => boolean;
14
- };
15
- routes: {
16
- readonly path: string;
17
- };
18
- components: {
19
- readonly path: string;
20
- readonly componentNameAttribute: string;
21
- };
22
- session: {
23
- readonly cookieName: string;
24
- readonly keyLength: number;
25
- readonly durationSeconds: number;
26
- readonly garbageCollectIntervalSeconds: number;
27
- };
28
- http: {
29
- host?: string;
30
- port: number;
31
- linkHeaderRel: 'preload' | 'preconnect';
32
- };
33
- readonly runtime: 'Node.js' | 'Deno';
34
- };
35
- export type StructuredClientConfig = {
36
- componentRender: StructuredConfig['url']['componentRender'];
37
- componentNameAttribute: string;
38
- };
39
- export type RequestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
40
- export type RequestCallback<R extends any, Body extends LooseObject | undefined> = (ctx: RequestContext<Body>) => Promise<R>;
41
- export type RequestHandler = {
42
- match: Array<URISegmentPattern> | RegExp;
43
- methods: Array<RequestMethod>;
44
- callback: RequestCallback<any, LooseObject | undefined>;
45
- scope: any;
46
- staticAsset: boolean;
47
- };
48
- export type RequestContext<Body extends LooseObject | undefined = LooseObject> = {
49
- request: IncomingMessage;
50
- response: ServerResponse;
51
- args: URIArguments;
52
- handler: null | RequestHandler;
53
- cookies: Record<string, string>;
54
- body: Body;
55
- bodyRaw?: Buffer;
56
- files?: Record<string, RequestBodyRecordValue>;
57
- data: RequestContextData;
58
- sessionId?: string;
59
- isAjax: boolean;
60
- getArgs: PostedDataDecoded;
61
- respondWith: (data: any) => void;
62
- redirect: (to: string, statusCode?: number) => void;
63
- show404: () => Promise<void>;
64
- };
65
- export type PostedDataDecoded = Record<string, string | boolean | Array<string | boolean | PostedDataDecoded> | Record<string, string | boolean | Array<string | boolean | PostedDataDecoded>> | Record<string, string | boolean | Array<string | boolean>>>;
66
- export type RequestBodyRecordValue = string | Array<RequestBodyRecordValue> | {
67
- [key: string]: RequestBodyRecordValue;
68
- } | {
69
- [key: string]: RequestBodyFile;
70
- } | Array<RequestBodyFile> | RequestBodyFile;
71
- export interface RequestBodyArguments {
72
- [key: string]: RequestBodyRecordValue;
73
- [symbolArrays]?: {
74
- [key: string]: Array<string>;
75
- };
76
- }
77
- export type RequestBodyFiles = {
78
- [key: string]: RequestBodyFile;
79
- };
80
- export type RequestBodyFile = {
81
- fileName: string;
82
- data: Buffer;
83
- type: string;
84
- };
85
- export type URISegmentPattern = {
86
- pattern: string | RegExp;
87
- name?: string;
88
- type?: 'string' | 'number';
89
- };
90
- export type URIArguments = {
91
- [key: string]: string | number | RegExpExecArray;
92
- };
93
- export type DocumentResource = {
94
- path: string;
95
- attributes: Record<string, string | null>;
96
- priority: number;
97
- };
98
- export type ComponentEntry = {
99
- name: string;
100
- path: {
101
- absolute: string;
102
- relative: string;
103
- relativeToViews: string;
104
- build: string;
105
- html: string;
106
- jsServer?: string;
107
- jsClient?: string;
108
- };
109
- hasJS: boolean;
110
- html: string;
111
- static: boolean;
112
- module?: ComponentScaffold;
113
- renderTagName?: string;
114
- exportData: boolean;
115
- exportFields?: Array<string>;
116
- attributes?: Record<string, string>;
117
- initializer?: InitializerFunction;
118
- };
119
- export interface ComponentScaffold {
120
- tagName?: string;
121
- exportData?: boolean;
122
- exportFields?: Array<string>;
123
- static?: boolean;
124
- deferred?: (data: Record<string, any>, ctx: RequestContext | undefined, app: Application) => boolean;
125
- attributes?: Record<string, string>;
126
- getData: (this: ComponentScaffold, data: RequestBodyArguments | LooseObject, ctx: undefined | RequestContext, app: Application, component: Component) => Promise<LooseObject | null>;
127
- [key: string]: any;
128
- }
129
- export type LooseObject = Record<string, any>;
130
- export type ApplicationEvents = 'serverStarted' | 'beforeRequestHandler' | 'afterRequestHandler' | 'beforeRoutes' | 'afterRoutes' | 'beforeComponentsLoad' | 'afterComponentsLoaded' | 'documentCreated' | 'beforeAssetAccess' | 'afterAssetAccess' | 'pageNotFound';
131
- export type SessionEntry = {
132
- sessionId: string;
133
- lastRequest: number;
134
- data: LooseObject;
135
- };
136
- export type ValidationRuleWithArguments = [string, any];
137
- export type FormValidationEntry = {
138
- field: [string, string];
139
- rules: Array<string | ValidationRuleWithArguments | ValidatorFunction>;
140
- };
141
- export type ValidatorFunction = (data: PostedDataDecoded, field: string, arg: number, rules: Array<string | ValidationRuleWithArguments | ValidatorFunction>) => Promise<boolean>;
142
- export type ValidatorErrorDecorator = (fieldHumanReadable: string, data: PostedDataDecoded, field: string, arg: any) => string | Promise<string>;
143
- export type ValidationErrors = {
144
- [field: string]: Array<string>;
145
- };
146
- export type ValidationErrorsSingle = {
147
- [field: string]: string;
148
- };
149
- export type ValidationResult = {
150
- valid: boolean;
151
- errors: ValidationErrors | ValidationErrorsSingle;
152
- };
153
- export type InitializerFunction = (this: ClientComponent, ctx: InitializerFunctionContext) => Promise<void>;
154
- export type Initializers = {
155
- [key: string]: InitializerFunction;
156
- };
157
- export type InitializerFunctionContext = {
158
- net: Net;
159
- isRedraw: boolean;
160
- };
161
- export type StoreChangeCallback = (key: string, value: any, oldValue: any, componentId: string) => void;
162
- export type ClientComponentEventCallback<T> = (e: Event, data: T, element: HTMLElement | Window) => void;
163
- export type ClientComponentBoundEvent<T extends LooseObject | undefined = undefined> = {
164
- element: HTMLElement | Window;
165
- event: keyof HTMLElementEventMap;
166
- callback: (e: Event) => void;
167
- callbackOriginal: ClientComponentEventCallback<T>;
168
- };
169
- export type ClientComponentTransition = {
170
- fade: false | number;
171
- slide: false | number;
172
- };
173
- export type ClientComponentTransitionEvent = 'show' | 'hide';
174
- export type ClientComponentTransitions = Record<ClientComponentTransitionEvent, ClientComponentTransition>;
175
- export type EventEmitterCallback<T> = (payload: T) => void;
1
+ export * from './types/request.types.js';
2
+ export * from './types/document.types.js';
3
+ export * from './types/component.types.js';
4
+ export * from './types/session.types.js';
5
+ export * from './types/store.types.js';
6
+ export * from './types/validation.types.js';
7
+ export * from './types/application.types.js';
8
+ export * from './types/eventEmitter.types.js';
9
+ export * from './types/general.types.js';
10
+ export * from './types/structured.types.js';
@@ -1 +1,10 @@
1
- import { symbolArrays } from "./Symbols.js";
1
+ export * from './types/request.types.js';
2
+ export * from './types/document.types.js';
3
+ export * from './types/component.types.js';
4
+ export * from './types/session.types.js';
5
+ export * from './types/store.types.js';
6
+ export * from './types/validation.types.js';
7
+ export * from './types/application.types.js';
8
+ export * from './types/eventEmitter.types.js';
9
+ export * from './types/general.types.js';
10
+ export * from './types/structured.types.js';
@@ -1,4 +1,5 @@
1
- import { LooseObject, PostedDataDecoded } from "./Types.js";
1
+ import { LooseObject } from './types/general.types.js';
2
+ import { PostedDataDecoded } from "./types/request.types.js";
2
3
  export declare function queryStringDecode(queryString: string, initialValue?: PostedDataDecoded, trimValues?: boolean): PostedDataDecoded;
3
4
  export declare function queryStringDecodedSetValue(obj: PostedDataDecoded | string, value: any): LooseObject;
4
5
  export declare function objectEach<T>(obj: T, callbackEach: (key: keyof T, value: T[keyof T]) => void): void;
@@ -6,7 +7,7 @@ export declare function toCamelCase(dataKey: string, separator?: string): string
6
7
  export declare function toSnakeCase(str: string, joinWith?: string): string;
7
8
  export declare function capitalize(str: string): string;
8
9
  export declare function isAsync(fn: Function): boolean;
9
- export declare function randomString(len: number): string;
10
+ export declare function randomString(len: number, method?: 'alphanumeric' | 'numbers' | 'letters' | 'lettersUppercase' | 'lettersLowercase'): string;
10
11
  export declare function unique<T>(arr: Array<T>): Array<T>;
11
12
  export declare function stripTags(contentWithHTML: string, keepTags?: Array<string>): string;
12
13
  export declare function attributeValueToString(key: string, value: any): string;
@@ -168,7 +168,7 @@ export function capitalize(str) {
168
168
  export function isAsync(fn) {
169
169
  return fn.constructor.name === 'AsyncFunction';
170
170
  }
171
- export function randomString(len) {
171
+ export function randomString(len, method = 'alphanumeric') {
172
172
  const charCodes = new Uint8Array(len);
173
173
  const generators = [
174
174
  function () {
@@ -181,8 +181,24 @@ export function randomString(len) {
181
181
  return 48 + Math.floor(Math.random() * 10);
182
182
  }
183
183
  ];
184
+ const generatorsUsed = [];
185
+ if (method === 'alphanumeric') {
186
+ generatorsUsed.push(...generators);
187
+ }
188
+ else if (method === 'numbers') {
189
+ generatorsUsed.push(generators[2]);
190
+ }
191
+ else if (method === 'letters') {
192
+ generatorsUsed.push(...generators.slice(0, 2));
193
+ }
194
+ else if (method === 'lettersLowercase') {
195
+ generatorsUsed.push(generators[1]);
196
+ }
197
+ else if (method === 'lettersUppercase') {
198
+ generatorsUsed.push(generators[0]);
199
+ }
184
200
  for (let i = 0; i < len; i++) {
185
- charCodes[i] = generators[Math.floor(Math.random() * generators.length)]();
201
+ charCodes[i] = generatorsUsed[Math.floor(Math.random() * generatorsUsed.length)]();
186
202
  }
187
203
  return String.fromCodePoint(...charCodes);
188
204
  }
@@ -1,4 +1,5 @@
1
- import { ClientComponentEventCallback, LooseObject } from '../Types.js';
1
+ import { LooseObject } from '../types/general.types.js';
2
+ import { ClientComponentEventCallback } from '../types/component.types.js';
2
3
  import { DataStoreView } from './DataStoreView.js';
3
4
  import { DataStore } from './DataStore.js';
4
5
  import { Net } from './Net.js';
@@ -277,21 +277,6 @@ export class ClientComponent extends EventEmitter {
277
277
  if (node === undefined) {
278
278
  node = this.domNode;
279
279
  }
280
- const modelData = (node) => {
281
- const field = node.getAttribute('data-model');
282
- if (field) {
283
- const isCheckbox = node.tagName === 'INPUT' && node.type === 'checkbox';
284
- const valueRaw = isCheckbox ? node.checked : node.value;
285
- const value = queryStringDecodedSetValue(field, valueRaw);
286
- return value;
287
- }
288
- return {};
289
- };
290
- const update = (data) => {
291
- objectEach(data, (key, val) => {
292
- this.store.set(key, val);
293
- });
294
- };
295
280
  if (node.hasAttribute('data-model') && (node.tagName === 'INPUT' || node.tagName === 'SELECT' || node.tagName === 'TEXTAREA')) {
296
281
  modelNodes.push(node);
297
282
  }
@@ -303,6 +288,53 @@ export class ClientComponent extends EventEmitter {
303
288
  });
304
289
  }
305
290
  if (isSelf) {
291
+ const modelData = (node) => {
292
+ const field = node.getAttribute('data-model');
293
+ if (field) {
294
+ const isCheckbox = node.tagName === 'INPUT' && node.type === 'checkbox';
295
+ const valueRaw = isCheckbox ? node.checked : node.value;
296
+ let valueCasted = valueRaw;
297
+ if (!isCheckbox && typeof valueRaw === 'string') {
298
+ const dataType = isCheckbox ? 'boolean' : node.getAttribute('data-type') || 'string';
299
+ const nullable = node.hasAttribute('data-nullable');
300
+ if (nullable && valueRaw.trim().length === 0) {
301
+ valueCasted = null;
302
+ }
303
+ else {
304
+ if (dataType === 'number') {
305
+ if (valueRaw.trim().length === 0) {
306
+ valueCasted = 0;
307
+ }
308
+ else {
309
+ const num = parseFloat(valueRaw);
310
+ if (isNaN(num)) {
311
+ valueCasted = nullable ? null : 0;
312
+ }
313
+ else {
314
+ valueCasted = num;
315
+ }
316
+ }
317
+ }
318
+ else if (dataType === 'boolean') {
319
+ if (valueRaw === '1' || valueRaw === 'true') {
320
+ valueCasted = true;
321
+ }
322
+ else {
323
+ valueCasted = false;
324
+ }
325
+ }
326
+ }
327
+ }
328
+ const value = queryStringDecodedSetValue(field, valueCasted);
329
+ return value;
330
+ }
331
+ return {};
332
+ };
333
+ const update = (data) => {
334
+ objectEach(data, (key, val) => {
335
+ this.store.set(key, val);
336
+ });
337
+ };
306
338
  let data = {};
307
339
  modelNodes.forEach((modelNode) => {
308
340
  modelNode.addEventListener('input', () => {
@@ -321,10 +353,11 @@ export class ClientComponent extends EventEmitter {
321
353
  });
322
354
  const field = modelNode.getAttribute('data-model');
323
355
  if (field) {
324
- const isCheckbox = modelNode.tagName === 'INPUT' && modelNode.type === 'checkbox';
325
- const valueRaw = isCheckbox ? modelNode.checked : modelNode.value;
326
- const value = queryStringDecodedSetValue(field, valueRaw);
327
- data = mergeDeep(data, value);
356
+ const updateModel = modelNode.type !== 'radio' || modelNode.checked;
357
+ if (updateModel) {
358
+ const valueObject = modelData(modelNode);
359
+ data = mergeDeep(data, valueObject);
360
+ }
328
361
  }
329
362
  });
330
363
  update(data);
@@ -374,7 +407,7 @@ export class ClientComponent extends EventEmitter {
374
407
  return isTrue;
375
408
  }
376
409
  else {
377
- const parts = /^(!)?\s*([a-zA-Z]+[a-zA-Z0-9_]*)\s*((?:==)|(?:===)|(?:!=)|(?:!==)|<|>|(?:<=)|(?:>=))?\s*([^=].+)?$/.exec(condition);
410
+ const parts = /^(!)?\s*([a-zA-Z0-9_]+)\s*((?:==)|(?:===)|(?:!=)|(?:!==)|<|>|(?:<=)|(?:>=))?\s?([^=]+)?$/.exec(condition);
378
411
  if (parts === null) {
379
412
  console.error(`Could not parse condition ${condition}`);
380
413
  return false;
@@ -586,25 +619,41 @@ export class ClientComponent extends EventEmitter {
586
619
  const onTransitionEnd = (e) => {
587
620
  domNode.style.opacity = '1';
588
621
  domNode.style.transition = '';
589
- domNode.style.transformOrigin = 'unset';
622
+ domNode.style.transformOrigin = '';
623
+ domNode.style.transform = '';
590
624
  domNode.removeEventListener('transitionend', onTransitionEnd);
591
625
  domNode.removeEventListener('transitioncancel', onTransitionEnd);
592
626
  };
593
627
  domNode.addEventListener('transitionend', onTransitionEnd);
594
628
  domNode.addEventListener('transitioncancel', onTransitionEnd);
595
629
  if (transitionsActive.slide) {
630
+ const axis = this.transitionAxis(domNode, 'show');
631
+ let slideDirection = axis === 'X' ? 'left' : 'up';
632
+ const invert = domNode.hasAttribute('data-transition-slide-invert');
633
+ if (invert) {
634
+ slideDirection = slideDirection === 'left' ? 'right' : (slideDirection === 'up' ? 'down' : 'up');
635
+ }
636
+ const slideLengthMultiplier = slideDirection === 'down' || slideDirection === 'right' ? -1 : 1;
637
+ const slideLength = (axis === 'X' ? domNode.clientWidth : domNode.clientHeight) * 0.5 * slideLengthMultiplier * -1;
638
+ domNode.style.transform = `translate${axis === 'X' ? 'X' : 'Y'}(${slideLength}px)`;
639
+ setTimeout(() => {
640
+ domNode.style.transition = `transform ${transitionsActive.slide}ms linear`;
641
+ domNode.style.transform = `translate${axis === 'X' ? 'X' : 'Y'}(0)`;
642
+ }, 50);
643
+ }
644
+ if (transitionsActive.grow) {
596
645
  const transformOrigin = domNode.getAttribute('data-transform-origin-show') || '50% 0';
597
646
  domNode.style.transformOrigin = transformOrigin;
598
647
  const axis = this.transitionAxis(domNode, 'show');
599
648
  domNode.style.transform = `scale${axis}(0.01)`;
600
- domNode.style.transition = `transform ${transitionsActive.slide / 1000}s`;
649
+ domNode.style.transition = `transform ${transitionsActive.grow}ms`;
601
650
  setTimeout(() => {
602
651
  domNode.style.transform = `scale${axis}(1)`;
603
652
  }, 100);
604
653
  }
605
654
  if (transitionsActive.fade) {
606
655
  domNode.style.opacity = '0';
607
- domNode.style.transition = `opacity ${transitionsActive.fade / 1000}s`;
656
+ domNode.style.transition = `opacity ${transitionsActive.fade}ms`;
608
657
  setTimeout(() => {
609
658
  domNode.style.opacity = '1';
610
659
  }, 100);
@@ -641,20 +690,34 @@ export class ClientComponent extends EventEmitter {
641
690
  domNode.addEventListener('transitionend', onTransitionEnd);
642
691
  domNode.addEventListener('transitioncancel', onTransitionEnd);
643
692
  if (transitionsActive.slide) {
693
+ domNode.style.transition = `transform ${transitionsActive.slide}ms linear`;
694
+ const axis = this.transitionAxis(domNode, 'hide');
695
+ let slideDirection = axis === 'X' ? 'left' : 'up';
696
+ const invert = domNode.hasAttribute('data-transition-slide-invert');
697
+ if (invert) {
698
+ slideDirection = slideDirection === 'left' ? 'right' : (slideDirection === 'up' ? 'down' : 'up');
699
+ }
700
+ setTimeout(() => {
701
+ const slideLengthMultiplier = slideDirection === 'down' || slideDirection === 'right' ? -1 : 1;
702
+ const slideLength = (axis === 'X' ? domNode.clientWidth : domNode.clientHeight) * 0.5 * slideLengthMultiplier * -1;
703
+ domNode.style.transform = `translate${axis === 'X' ? 'X' : 'Y'}(${slideLength}px)`;
704
+ }, 50);
705
+ }
706
+ if (transitionsActive.grow) {
644
707
  const transformOrigin = domNode.getAttribute('data-transform-origin-hide') || '50% 100%';
645
708
  domNode.style.transformOrigin = transformOrigin;
646
- domNode.style.transition = `transform ${transitionsActive.slide / 1000}s ease`;
709
+ domNode.style.transition = `transform ${transitionsActive.grow}ms ease`;
647
710
  setTimeout(() => {
648
711
  const axis = this.transitionAxis(domNode, 'hide');
649
712
  domNode.style.transform = `scale${axis}(0.01)`;
650
- }, 100);
713
+ }, 50);
651
714
  }
652
715
  if (transitionsActive.fade) {
653
716
  domNode.style.opacity = '1';
654
- domNode.style.transition = `opacity ${transitionsActive.fade / 1000}s`;
717
+ domNode.style.transition = `opacity ${transitionsActive.fade}ms`;
655
718
  setTimeout(() => {
656
719
  domNode.style.opacity = '0';
657
- }, 100);
720
+ }, 50);
658
721
  }
659
722
  }
660
723
  }
@@ -662,11 +725,13 @@ export class ClientComponent extends EventEmitter {
662
725
  const transitions = {
663
726
  show: {
664
727
  slide: false,
665
- fade: false
728
+ fade: false,
729
+ grow: false,
666
730
  },
667
731
  hide: {
668
732
  slide: false,
669
- fade: false
733
+ fade: false,
734
+ grow: false,
670
735
  }
671
736
  };
672
737
  objectEach(transitions, (transitionEvent, transition) => {
@@ -1,4 +1,4 @@
1
- import { StoreChangeCallback } from '../Types.js';
1
+ import { StoreChangeCallback } from '../types/store.types.js';
2
2
  import { ClientComponent } from './ClientComponent.js';
3
3
  export declare class DataStore {
4
4
  protected data: {
@@ -1,4 +1,4 @@
1
- import { StoreChangeCallback } from '../Types.js';
1
+ import { StoreChangeCallback } from '../types/store.types.js';
2
2
  import { ClientComponent } from './ClientComponent.js';
3
3
  import { DataStore } from './DataStore.js';
4
4
  export declare class DataStoreView {
@@ -1,5 +1,6 @@
1
1
  import { IncomingHttpHeaders } from 'node:http';
2
- import { LooseObject, RequestMethod } from '../Types.js';
2
+ import { LooseObject } from '../types/general.types.js';
3
+ import { RequestMethod } from "../types/request.types.js";
3
4
  export declare class Net {
4
5
  request(method: RequestMethod, url: string, headers?: IncomingHttpHeaders, body?: any, responseType?: XMLHttpRequestResponseType): Promise<string>;
5
6
  get(url: string, headers?: IncomingHttpHeaders): Promise<string>;
@@ -1,5 +1,5 @@
1
1
  import { IncomingHttpHeaders } from 'node:http';
2
- import { RequestMethod } from '../Types.js';
2
+ import { RequestMethod } from "../types/request.types.js";
3
3
  export declare class NetRequest {
4
4
  xhr: XMLHttpRequest;
5
5
  method: RequestMethod;
@@ -0,0 +1 @@
1
+ export type RequestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,8 @@
1
1
  import { Server } from 'node:http';
2
- import { ApplicationEvents, LooseObject, RequestContext, StructuredConfig } from '../Types.js';
2
+ import { ApplicationEvents } from '../types/application.types.js';
3
+ import { StructuredConfig } from '../types/structured.types.js';
4
+ import { LooseObject } from '../types/general.types.js';
5
+ import { RequestContext } from "../types/request.types.js";
3
6
  import { Document } from './Document.js';
4
7
  import { Components } from './Components.js';
5
8
  import { Session } from './Session.js';
@@ -154,7 +154,7 @@ export class Application {
154
154
  const document = new Document(this, '', ctx);
155
155
  const data = attributes;
156
156
  await document.loadComponent(component.name, data);
157
- const exportedData = component.exportData ? document.data : (component.exportFields ? component.exportFields.reduce((prev, curr) => {
157
+ const exportedData = component.exportData ? document.children[0].data : (component.exportFields ? component.exportFields.reduce((prev, curr) => {
158
158
  prev[curr] = document.children[0].data[curr];
159
159
  return prev;
160
160
  }, {}) : {});
@@ -1,5 +1,6 @@
1
1
  import { Document } from './Document.js';
2
- import { ComponentEntry, LooseObject } from '../Types.js';
2
+ import { LooseObject } from '../types/general.types.js';
3
+ import { ComponentEntry } from "../types/component.types.js";
3
4
  import { DOMNode } from './dom/DOMNode.js';
4
5
  import { EventEmitter } from '../EventEmitter.js';
5
6
  export declare class Component<Events extends Record<string, any> = {