x4js 2.3.17 → 2.3.19

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 (46) hide show
  1. package/aicli.md +3 -3
  2. package/aicontext.md +211 -47
  3. package/cli/config.mjs +14 -7
  4. package/cli/copy-plugin.mjs +8 -0
  5. package/cli/create.mjs +8 -2
  6. package/cli/dev.mjs +4 -1
  7. package/cli/less-plugin.mjs +5 -1
  8. package/cli/main.mjs +15 -5
  9. package/cli/rawfile-plugin.mjs +23 -3
  10. package/cli/templates.mjs +5 -1
  11. package/package.json +1 -1
  12. package/src/components/calendar/calendar.module.scss +16 -1
  13. package/src/components/calendar/calendar.ts +52 -9
  14. package/src/components/checkbox/checkbox.ts +1 -0
  15. package/src/components/combobox/combobox.ts +51 -23
  16. package/src/components/dialog/dialog.ts +4 -1
  17. package/src/components/form/form.ts +9 -5
  18. package/src/components/gridview/gridview.ts +352 -176
  19. package/src/components/input/input.ts +63 -38
  20. package/src/components/keyboard/keyboard.module.scss +35 -4
  21. package/src/components/keyboard/keyboard.ts +295 -80
  22. package/src/components/listbox/listbox.ts +6 -1
  23. package/src/components/messages/messages.ts +12 -3
  24. package/src/components/popup/popup.ts +19 -20
  25. package/src/components/radio/radio.ts +8 -4
  26. package/src/components/rating/rating.ts +38 -48
  27. package/src/components/slider/slider.ts +38 -8
  28. package/src/components/spreadsheet/spreadsheet.ts +167 -132
  29. package/src/components/switch/switch.ts +2 -1
  30. package/src/components/tag/tag.ts +1 -2
  31. package/src/components/textarea/textarea.module.scss +6 -2
  32. package/src/components/textarea/textarea.ts +50 -11
  33. package/src/components/textedit/textedit.ts +12 -2
  34. package/src/core/component.ts +16 -9
  35. package/src/core/core_application.ts +8 -4
  36. package/src/core/core_clipboard.ts +239 -0
  37. package/src/core/core_data.ts +118 -49
  38. package/src/core/core_dom.ts +36 -6
  39. package/src/core/core_dragdrop.ts +11 -5
  40. package/src/core/core_element.ts +1 -1
  41. package/src/core/core_pdf.ts +696 -46
  42. package/src/core/core_router.ts +42 -44
  43. package/src/core/core_shortcuts.ts +1 -1
  44. package/src/core/core_state.ts +22 -5
  45. package/src/x4.d.ts +1 -0
  46. package/src/x4.ts +1 -0
@@ -92,6 +92,23 @@ export function parseRoute(str: string | RegExp, loose = false): Segment {
92
92
  };
93
93
  }
94
94
 
95
+ /**
96
+ * the parameters of the url are encoded (%C3%A9): decoded; a lone % is left as it is
97
+ */
98
+
99
+ function decode( s: string ) {
100
+ if( s===undefined ) {
101
+ return s; // an optional parameter that is not there
102
+ }
103
+
104
+ try {
105
+ return decodeURIComponent( s );
106
+ }
107
+ catch {
108
+ return s;
109
+ }
110
+ }
111
+
95
112
  /**
96
113
  * Defines the events that the `Router` can emit.
97
114
  */
@@ -144,15 +161,13 @@ export class Router extends EventSource< RouterEvents > {
144
161
  const url = this._getLocation( );
145
162
  const found = this._find(url);
146
163
 
147
- if( found.handlers.length==0 ) {
164
+ if( !found ) {
148
165
  console.log( 'route not found: '+url );
149
166
  this.fire( "error", {code: 404, message: "route not found" } );
150
167
  return;
151
168
  }
152
169
 
153
- found.handlers.forEach(h => {
154
- h(found.params,url);
155
- });
170
+ found.handler( found.params, url );
156
171
 
157
172
  this.fire( "change", { value: this._getLocation() } );
158
173
  });
@@ -201,7 +216,8 @@ export class Router extends EventSource< RouterEvents > {
201
216
  * Behavior summary:
202
217
  * - If no matching route or no handlers are found, logs a message, fires an "error" event with
203
218
  * { code: 404, message: "route not found" } and returns false.
204
- * - If notify is true, invokes the first handler of the matched route with (params, uri).
219
+ * - If notify is true, invokes the handler of the first route that matches, in the order of get,
220
+ * with (params, path): the params decoded, the path as the back button gives it ("/detail/5").
205
221
  * - Always fires a "change" event with { value: this._getLocation() } after performing the navigation.
206
222
  *
207
223
  * @param uri - Target URI to navigate to. If it does not start with '/', a leading '/' will be added.
@@ -227,7 +243,7 @@ export class Router extends EventSource< RouterEvents > {
227
243
 
228
244
  const found = this._find( uri );
229
245
 
230
- if( !found || found.handlers.length==0 ) {
246
+ if( !found ) {
231
247
  //window.history.pushState({}, '', 'error')
232
248
  console.log( 'route not found: '+uri );
233
249
  this.fire( "error", {code: 404, message: "route not found" } );
@@ -249,12 +265,9 @@ export class Router extends EventSource< RouterEvents > {
249
265
  window.history.pushState({}, '', uri );
250
266
  }
251
267
 
268
+ // the path as popstate gives it: "/detail/5", encoded as in the address
252
269
  if( notify ) {
253
- //found.handlers.forEach( h => {
254
- // h( found.params, uri );
255
- //} );
256
-
257
- found.handlers[0]( found.params, uri );
270
+ found.handler( found.params, this._getLocation( ) );
258
271
  }
259
272
 
260
273
  this.fire( "change", { value: this._getLocation() } );
@@ -262,47 +275,32 @@ export class Router extends EventSource< RouterEvents > {
262
275
  }
263
276
 
264
277
  /**
265
- *
278
+ * the first route that matches, in the order of get, and its parameters decoded; null when none
266
279
  */
267
280
 
268
- private _find( url: string ): { params: Record<string,any>, handlers: RouteHandler[] } {
269
-
270
- let matches = [];
271
- let params: Record<string,any> = {};
272
- let handlers: RouteHandler[] = [];
281
+ private _find( url: string ): { params: Record<string,any>, handler: RouteHandler } {
273
282
 
274
283
  for (const tmp of this.m_routes ) {
275
- if (!tmp.keys ) {
276
- matches = tmp.pattern.exec(url);
277
- if (!matches) {
278
- continue;
279
- }
280
-
281
- if (matches['groups']) {
282
- for (const k in matches['groups']) {
283
- params[k] = matches['groups'][k];
284
- }
285
- }
286
-
287
- handlers = [...handlers, tmp.handler];
288
- }
289
- else if (tmp.keys.length > 0) {
290
- matches = tmp.pattern.exec(url);
291
- if (matches === null) {
292
- continue;
293
- }
284
+ const matches = tmp.pattern.exec( url );
285
+ if( !matches ) {
286
+ continue;
287
+ }
294
288
 
295
- for ( let j = 0; j < tmp.keys.length;) {
296
- params[tmp.keys[j]] = matches[++j];
289
+ const params: Record<string,any> = {};
290
+ if( !tmp.keys ) {
291
+ for (const k in matches.groups ) {
292
+ params[k] = decode( matches.groups[k] );
297
293
  }
298
-
299
- handlers = [...handlers, tmp.handler];
300
- }
301
- else if (tmp.pattern.test(url)) {
302
- handlers = [...handlers, tmp.handler];
303
294
  }
295
+ else {
296
+ tmp.keys.forEach( ( k, j ) => {
297
+ params[k] = decode( matches[j+1] );
298
+ } );
299
+ }
300
+
301
+ return { params, handler: tmp.handler };
304
302
  }
305
303
 
306
- return { params, handlers };
304
+ return null;
307
305
  }
308
306
  }
@@ -51,7 +51,7 @@ interface Keys {
51
51
  key: string;
52
52
  }
53
53
 
54
- const isMac = typeof navigator!=="undefined" && /mac|iphone|ipad/i.test( navigator.platform ?? "" );
54
+ const isMac = /mac|iphone|ipad/i.test( navigator.platform ?? "" );
55
55
 
56
56
  /**
57
57
  * "Shift+Mod+K" -> shift, ctrl (cmd on a Mac), k
@@ -75,8 +75,11 @@ function _path_matches( path: string, watched: string ): boolean {
75
75
  return c==='.' || c==='[';
76
76
  }
77
77
 
78
+ // asked to a proxy of the state, it gives its raw object
79
+ const RAW = Symbol( "raw" );
80
+
78
81
  /**
79
- *
82
+ *
80
83
  */
81
84
 
82
85
  export class StateManager<T extends State> extends EventSource<StateEvents> {
@@ -90,9 +93,11 @@ export class StateManager<T extends State> extends EventSource<StateEvents> {
90
93
  * - guarantees referential identity: state.user === state.user
91
94
  * - WeakMap: entries are collected with their objects, and the whole cache is collected with the manager
92
95
  * - instance-scoped (not module-level) so the same raw object referenced by two different managers never gets a proxy bound to the wrong manager/path
96
+ * - a proxy by object, made for the path it was read at: an object moved
97
+ * in an array (shift, sort...) gets a proxy at its new path
93
98
  */
94
99
 
95
- private _cache = new WeakMap<object, any>( );
100
+ private _cache = new WeakMap<object, { path: string, proxy: any }>( );
96
101
 
97
102
  constructor( initialState: T ) {
98
103
  super( );
@@ -127,12 +132,17 @@ export class StateManager<T extends State> extends EventSource<StateEvents> {
127
132
  private _mk_proxy( obj: any, path: string ): any {
128
133
 
129
134
  const cached = this._cache.get( obj );
130
- if( cached ) return cached;
135
+ if( cached && cached.path===path ) return cached.proxy;
131
136
 
132
137
  const proxy = new Proxy( obj, {
133
138
 
134
139
  get: ( target, prop, receiver ) => {
135
140
 
141
+ // the raw object, for set
142
+ if( prop === RAW ) {
143
+ return target;
144
+ }
145
+
136
146
  // symbols (Symbol.iterator, Symbol.toPrimitive, devtools inspection...):
137
147
  // pass-through, never an error nor a wrap
138
148
  if( typeof prop === "symbol" ) {
@@ -161,9 +171,16 @@ export class StateManager<T extends State> extends EventSource<StateEvents> {
161
171
 
162
172
  set: ( target, prop, value, receiver ) => {
163
173
 
174
+ // shift, sort, reverse... read the elements through the proxy and write them
175
+ // back: the raw object is stored, never a proxy
176
+ if( value && value[RAW] ) {
177
+ value = value[RAW];
178
+ }
179
+
164
180
  // no notification when the value doesn't change
165
181
  // (avoids noise and listener → set → listener loops)
166
- if( Reflect.get( target, prop, receiver ) === value ) {
182
+ // read without the receiver: through the proxy, an object would be a proxy, never the raw value
183
+ if( Reflect.get( target, prop ) === value ) {
167
184
  return true;
168
185
  }
169
186
 
@@ -191,7 +208,7 @@ export class StateManager<T extends State> extends EventSource<StateEvents> {
191
208
  }
192
209
  });
193
210
 
194
- this._cache.set( obj, proxy );
211
+ this._cache.set( obj, { path, proxy } );
195
212
  return proxy;
196
213
  }
197
214
 
package/src/x4.d.ts CHANGED
@@ -15,6 +15,7 @@
15
15
  **/
16
16
  export * from "./core/component";
17
17
  export * from "./core/core_application";
18
+ export * from "./core/core_clipboard";
18
19
  export * from "./core/core_colors";
19
20
  export * from "./core/core_data";
20
21
  export * from "./core/core_dom";
package/src/x4.ts CHANGED
@@ -16,6 +16,7 @@
16
16
 
17
17
  export * from "./core/component"
18
18
  export * from "./core/core_application"
19
+ export * from "./core/core_clipboard"
19
20
  export * from "./core/core_colors"
20
21
  export * from "./core/core_data"
21
22
  export * from "./core/core_dom"