@aquera/nile-elements 0.1.1 → 0.1.3

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 (32) hide show
  1. package/README.md +8 -0
  2. package/demo/variables.css +0 -556
  3. package/dist/nile-code-editor/extensionSetup.cjs.js +6 -6
  4. package/dist/nile-code-editor/extensionSetup.cjs.js.map +1 -1
  5. package/dist/nile-code-editor/extensionSetup.esm.js +1 -1
  6. package/dist/nile-code-editor/nile-code-editor.cjs.js +2 -2
  7. package/dist/nile-code-editor/nile-code-editor.cjs.js.map +1 -1
  8. package/dist/nile-code-editor/nile-code-editor.esm.js +3 -3
  9. package/dist/nile-drawer/nile-drawer.css.cjs.js +1 -1
  10. package/dist/nile-drawer/nile-drawer.css.cjs.js.map +1 -1
  11. package/dist/nile-drawer/nile-drawer.css.esm.js +1 -1
  12. package/dist/nile-form-help-text/nile-form-help-text.cjs.js +1 -1
  13. package/dist/nile-form-help-text/nile-form-help-text.cjs.js.map +1 -1
  14. package/dist/nile-form-help-text/nile-form-help-text.esm.js +2 -2
  15. package/dist/src/nile-code-editor/extensionSetup.d.ts +8 -9
  16. package/dist/src/nile-code-editor/extensionSetup.js +0 -13
  17. package/dist/src/nile-code-editor/extensionSetup.js.map +1 -1
  18. package/dist/src/nile-code-editor/nile-code-editor.d.ts +49 -15
  19. package/dist/src/nile-code-editor/nile-code-editor.js +195 -115
  20. package/dist/src/nile-code-editor/nile-code-editor.js.map +1 -1
  21. package/dist/src/nile-drawer/nile-drawer.css.js +1 -1
  22. package/dist/src/nile-drawer/nile-drawer.css.js.map +1 -1
  23. package/dist/src/nile-form-help-text/nile-form-help-text.js +32 -2
  24. package/dist/src/nile-form-help-text/nile-form-help-text.js.map +1 -1
  25. package/dist/tsconfig.tsbuildinfo +1 -1
  26. package/package.json +2 -1
  27. package/plop-templates/lit/lit.ts.hbs +2 -2
  28. package/src/nile-code-editor/extensionSetup.ts +8 -23
  29. package/src/nile-code-editor/nile-code-editor.ts +209 -124
  30. package/src/nile-drawer/nile-drawer.css.ts +1 -1
  31. package/src/nile-form-help-text/nile-form-help-text.ts +37 -3
  32. package/vscode-html-custom-data.json +13 -2
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Webcomponent nile-elements following open-wc recommendations",
4
4
  "license": "MIT",
5
5
  "author": "nile-elements",
6
- "version": "0.1.1",
6
+ "version": "0.1.3",
7
7
  "main": "dist/src/index.js",
8
8
  "type": "module",
9
9
  "module": "dist/src/index.js",
@@ -117,6 +117,7 @@
117
117
  "@codemirror/lang-javascript": "6.2.1",
118
118
  "@codemirror/lang-sql": "6.7.0",
119
119
  "@codemirror/lang-json": "^6.0.1",
120
+ "@codemirror/lang-html":"6.4.9",
120
121
  "codemirror": "6.0.1",
121
122
  "chalk": "5.3.0",
122
123
  "figlet": "1.7.0",
@@ -5,8 +5,8 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import {LitElement, html, property, CSSResultArray, TemplateResult} from 'lit';
9
- import { customElement } from 'lit/decorators.js';
8
+ import {LitElement, html, CSSResultArray, TemplateResult} from 'lit';
9
+ import { customElement, property} from 'lit/decorators.js';
10
10
  import {styles} from './nile-{{name}}.css';
11
11
  import NileElement from '../internal/nile-element';
12
12
 
@@ -30,6 +30,14 @@ import {
30
30
 
31
31
  import { lintKeymap } from '@codemirror/lint';
32
32
 
33
+ export interface MinimalSetupOptions {
34
+ highlightSpecialChars?: boolean;
35
+ history?: boolean;
36
+ drawSelection?: boolean;
37
+ syntaxHighlighting?: boolean;
38
+ defaultKeymap?: boolean;
39
+ historyKeymap?: boolean;
40
+ }
33
41
  export interface BasicSetupOptions extends MinimalSetupOptions {
34
42
  lineNumbers?: boolean;
35
43
  highlightActiveLineGutter?: boolean;
@@ -91,27 +99,4 @@ export const basicSetup = (options: BasicSetupOptions = {}): Extension[] => {
91
99
  return extensions.concat([keymap.of(keymaps.flat())]).filter(Boolean);
92
100
  };
93
101
 
94
- export interface MinimalSetupOptions {
95
- highlightSpecialChars?: boolean;
96
- history?: boolean;
97
- drawSelection?: boolean;
98
- syntaxHighlighting?: boolean;
99
- defaultKeymap?: boolean;
100
- historyKeymap?: boolean;
101
- }
102
102
 
103
- export const minimalSetup = (options: MinimalSetupOptions = {}) => {
104
- let keymaps: KeyBinding[] = [];
105
- isValidSetup(options.defaultKeymap) && keymaps.push(...defaultKeymap);
106
- isValidSetup(options.historyKeymap) && keymaps.push(...historyKeymap);
107
- const extensions: Extension[] = [];
108
- isValidSetup(options.highlightSpecialChars) &&
109
- extensions.push(highlightSpecialChars());
110
- isValidSetup(options.history) && extensions.push(history());
111
- isValidSetup(options.drawSelection) && extensions.push(drawSelection());
112
- isValidSetup(options.syntaxHighlighting) &&
113
- extensions.push(
114
- syntaxHighlighting(defaultHighlightStyle, { fallback: true })
115
- );
116
- return extensions.concat([keymap.of(keymaps.flat())]).filter(Boolean);
117
- };
@@ -6,7 +6,6 @@
6
6
  */
7
7
 
8
8
  import {
9
- LitElement,
10
9
  html,
11
10
  CSSResultArray,
12
11
  TemplateResult,
@@ -20,23 +19,23 @@ import { ViewUpdate, placeholder } from '@codemirror/view';
20
19
  import {
21
20
  Compartment,
22
21
  EditorState,
22
+ Extension
23
23
  } from '@codemirror/state';
24
24
 
25
25
  import { lineNumbers } from '@codemirror/view';
26
26
  import {
27
27
  javascript,
28
28
  javascriptLanguage,
29
- scopeCompletionSource,
30
29
  } from '@codemirror/lang-javascript';
31
30
  import { sql } from '@codemirror/lang-sql';
32
31
  import { json } from '@codemirror/lang-json';
32
+ import { html as htmlLang } from '@codemirror/lang-html';
33
33
  import { autocompletion,CompletionContext,CompletionResult } from '@codemirror/autocomplete';
34
34
  import NileElement from '../internal/nile-element';
35
35
  import { basicSetup } from './extensionSetup';
36
36
  import { classMap } from 'lit/directives/class-map.js';
37
37
  import { Theme } from './theme';
38
38
 
39
- const TIME_OUT_DURATION=200;
40
39
  // Choose the appropriate mode for your use case
41
40
 
42
41
  /**
@@ -58,7 +57,9 @@ export class NileCodeEditor extends NileElement {
58
57
 
59
58
  @property({ type: Object, reflect: true , attribute: true }) customAutoCompletions: object | any = {};
60
59
 
61
- @property({ type: String, reflect: true , attribute: true}) language: 'javascript' | 'sql' | 'json' = 'javascript';
60
+ @property({ type: Array, reflect: true , attribute: true }) customCompletionsPaths: string[] = [];
61
+
62
+ @property({ type: String, reflect: true , attribute: true}) language: 'javascript' | 'sql' | 'json' | 'html' = 'javascript';
62
63
 
63
64
  @property({ type: String, reflect: true , attribute: 'error-message' }) errorMessage: string = '';
64
65
 
@@ -82,6 +83,8 @@ export class NileCodeEditor extends NileElement {
82
83
 
83
84
  @property({ type: Boolean, reflect: true , attribute: true}) debounce: boolean = false;
84
85
 
86
+ @property({ type: Number, reflect: true , attribute: true}) debounceTimeout: number = 200;
87
+
85
88
  public view: EditorView;
86
89
  public viewState:EditorState;
87
90
  private timeOut: any = null;
@@ -157,7 +160,7 @@ export class NileCodeEditor extends NileElement {
157
160
  ]
158
161
  })
159
162
  }
160
- if(changedProperties.has('customAutoCompletions') || changedProperties.has('suggestionBracketSupport') ){
163
+ if(changedProperties.has('customAutoCompletions') || changedProperties.has('customCompletionsPaths')){
161
164
  this.view.dispatch({
162
165
  effects: [
163
166
  this.customCompletionComp.reconfigure(javascriptLanguage.data.of({
@@ -225,7 +228,7 @@ export class NileCodeEditor extends NileElement {
225
228
  }));
226
229
 
227
230
  this.viewState = EditorState.create({
228
- doc: !this.multiline ? this.convertToSingleLine(this.value) : this.value,
231
+ doc: !this.multiline ? convertToSingleLine(this.value) : this.value,
229
232
  extensions: [
230
233
  basicSetup({
231
234
  highlightActiveLine: false,
@@ -252,28 +255,112 @@ export class NileCodeEditor extends NileElement {
252
255
  });
253
256
  return this.viewState
254
257
  }
258
+ /**
259
+ * Custom autocomplete handler for code editor suggestions
260
+ * @param context CompletionContext from CodeMirror
261
+ * @returns CompletionResult with suggestions or null if no suggestions
262
+ */
263
+ customAutocomplete = (context: CompletionContext): CompletionResult | null => {
264
+ // Getting the valid last line, last text from the code editor
265
+ const text = context.state.doc.sliceString(0, context.pos);
266
+ const lastWord = text.split('\n').at(-1)?.split(' ').at(-1) || '';
267
+ const [textBeforeCursor, baseTextAfterSeperation] = splitStringAtLastSeparator(lastWord);
268
+
269
+ return this.getNestedSuggestions(context, textBeforeCursor, baseTextAfterSeperation)
270
+ || this.getTopLevelSuggestions(context, textBeforeCursor);
271
+ };
255
272
 
256
- singleLineMultiLineToggle() {
257
- this.view.dispatch({
258
- changes: {
259
- from: 0,
260
- to: this.view.state.doc.length,
261
- insert: !this.multiline
262
- ? this.convertToSingleLine(this.value)
263
- : this.value,
264
- },
265
- });
273
+ /**
274
+ * Gets nested property suggestions based on the current path
275
+ * @param context CompletionContext from CodeMirror
276
+ * @param textBeforeCursor Text before cursor position
277
+ * @param baseTextAfterSeperation Text after the last separator (. or [)
278
+ * @returns CompletionResult with nested suggestions or null
279
+ */
280
+ getNestedSuggestions(context: CompletionContext, textBeforeCursor: string, baseTextAfterSeperation: string) {
281
+ // Return early if not a valid path or not ending with . or [
282
+ if (!isValidPath(textBeforeCursor) || !['.', '['].includes(textBeforeCursor.at(-1)!)) {
283
+ return null;
284
+ }
285
+
286
+ const path = parsePath(textBeforeCursor);
287
+ if (!path) return null;
288
+
289
+ const textAfterSeperation = baseTextAfterSeperation.replace(/["'\[]/g, '');
290
+ const isInString = textAfterSeperation !== baseTextAfterSeperation;
291
+ const isBracket = textBeforeCursor.at(-1) === '[';
292
+
293
+ // Return null if we're in a string after a dot
294
+ if (textBeforeCursor.at(-1) === '.' && isInString) return null;
295
+
296
+ // Get nested properties and filter by text after separation if it exists
297
+ let resolved = resolveNestedProperties(this.customAutoCompletions, path);
298
+ if (!resolved || typeof resolved !== 'object') return null;
299
+
300
+ if (textAfterSeperation) {
301
+ resolved = Object.fromEntries(
302
+ Object.entries(resolved).filter(([key]) =>
303
+ key.toLowerCase().startsWith(textAfterSeperation.toLowerCase())
304
+ )
305
+ );
306
+ }
307
+
308
+ return {
309
+ from: context.pos - textAfterSeperation.length,
310
+ options: Object.keys(resolved).map(key => ({
311
+ label: key,
312
+ type: 'property',
313
+ info: `Key of ${path[path.length - 1]}`,
314
+ apply: !this.allowVariableInCustomSuggestion && (isBracket && !isInString)
315
+ ? `'${key}'`
316
+ : key,
317
+ boost: 999
318
+ }))
319
+ };
266
320
  }
267
321
 
268
- convertToSingleLine(code: any) {
269
- if (!code) return '';
270
- // Remove line breaks and unnecessary whitespace
271
- return code.replace(/\s+/g, ' ').trim();
322
+ /**
323
+ * Gets top level suggestions based on custom completions and paths
324
+ * @param context CompletionContext from CodeMirror
325
+ * @param textBeforeCursor Text before cursor position
326
+ * @returns CompletionResult with top level suggestions or null
327
+ */
328
+ getTopLevelSuggestions(context: CompletionContext,textBeforeCursor:string){
329
+ const baseMatch: any = textBeforeCursor.match(/([a-zA-Z_$][\w$]*)$/);
330
+ if (!baseMatch) return null;
331
+
332
+ const optionsList = Object.keys(this.customAutoCompletions).filter(key =>
333
+ Object.keys(this.customAutoCompletions[key]).length &&
334
+ key.toLowerCase().startsWith(textBeforeCursor.toLowerCase())
335
+ );
336
+
337
+ const options=optionsList.map((key) => ({
338
+ label: key,
339
+ type: 'property',
340
+ apply: key,
341
+ boost: 999
342
+ }))
343
+ if(this.customCompletionsPaths.length){
344
+ this.customCompletionsPaths
345
+ .filter(path=>path.toLocaleLowerCase().includes(textBeforeCursor.toLocaleLowerCase()))
346
+ .map(path=>{
347
+ options.push({
348
+ label: ''+path,
349
+ type: 'property',
350
+ apply: ''+path,
351
+ boost: 998
352
+ })
353
+ })
354
+ }
355
+ return {
356
+ from: context.pos - baseMatch[1].length,
357
+ options: options
358
+ }
272
359
  }
273
360
 
274
361
  emitAfterTimeout(value:any){
275
362
  if(this.timeOut) clearTimeout(this.timeOut);
276
- this.timeOut=setTimeout(()=> this.emit('nile-change', value, false), TIME_OUT_DURATION)
363
+ this.timeOut=setTimeout(()=> this.emit('nile-change', value, false), this.debounceTimeout)
277
364
  }
278
365
 
279
366
  public insertBetweenCode=(text: string) => {
@@ -283,18 +370,32 @@ export class NileCodeEditor extends NileElement {
283
370
  });
284
371
  this.view.dispatch(transaction);
285
372
  }
286
-
373
+
374
+ singleLineMultiLineToggle() {
375
+ this.view.dispatch({
376
+ changes: {
377
+ from: 0,
378
+ to: this.view.state.doc.length,
379
+ insert: !this.multiline
380
+ ? convertToSingleLine(this.value)
381
+ : this.value,
382
+ },
383
+ });
384
+ }
385
+
287
386
  //EXTENSION CONFIGURATIONS
288
387
  getLineNumbersExension() {
289
388
  return (!this.multiline && this.lineNumbers) || (this.multiline && this.lineNumbersMultiline) ? lineNumbers() : [];
290
389
  }
291
390
 
292
- getLanguageExtension(){
391
+ getLanguageExtension():Extension{
293
392
  switch(this.language){
294
393
  case 'sql':
295
394
  return sql();
296
395
  case 'json':
297
396
  return json();
397
+ case 'html':
398
+ return htmlLang();
298
399
  default:
299
400
  return javascript();
300
401
  }
@@ -319,115 +420,99 @@ export class NileCodeEditor extends NileElement {
319
420
  tr.newDoc.lines > 1 ? [] : tr
320
421
  );
321
422
  }
423
+ /* #endregion */
424
+ }
322
425
 
323
- customAutocomplete = (context: CompletionContext): CompletionResult | null => {
324
- // Getting the valid last line, last text from the code editor
325
- let text = context.state.doc.sliceString(0, context.pos);
326
- const [textBeforeCursor, baseTextAfterSeperation] = this.splitStringAtLastSeparator(text.split('\n').at(-1)?.split(' ').at(-1) + '')
327
- const textAfterSeperation = baseTextAfterSeperation.replace(/["'\[]/g, '')
328
-
329
- const isInString = textAfterSeperation != baseTextAfterSeperation;
330
- const isBracket = textBeforeCursor.at(-1) == '[';
331
- const isDot = textBeforeCursor.at(-1) == '.';
332
-
333
- if (!this.isValidPath(textBeforeCursor)) return { from: context.pos, options: [] };
334
- if (['.', '['].includes(textBeforeCursor[textBeforeCursor.length - 1])) {
335
- // Parse the path for dot or bracket notation
336
- const path = this.parsePath(textBeforeCursor);
337
- if (path) {
338
- if (isDot && isInString) return null;
339
- let resolved = this.resolveNestedProperties(this.customAutoCompletions, path);
340
- if (textAfterSeperation) {
341
- const obj: any = {}
342
- Object.keys(resolved).forEach((key) => {
343
- if (key.toLowerCase().startsWith(textAfterSeperation.toLowerCase())) {
344
- obj[key] = resolved[key]
345
- }
346
- })
347
- resolved = obj
348
- }
349
- // If resolved is an object, provide its keys as suggestions
350
- if (resolved && typeof resolved === 'object') {
351
- return {
352
- from: context.pos - textAfterSeperation.length,
353
- options: Object.keys(resolved).map((key) => ({
354
- label: key,
355
- type: 'property',
356
- info: `Key of ${path[path.length - 1]}`,
357
- apply: !this.allowVariableInCustomSuggestion && (isBracket && !isInString) ? "\'" + key + "\'" : key,
358
- boost: 999
359
- })),
360
- };
361
- }
362
- }
363
- }
364
-
365
- // Match for top-level object suggestions, e.g., "a"
366
- const baseMatch: any = textBeforeCursor.match(/([a-zA-Z_$][\w$]*)$/);
367
- if (baseMatch) {
368
- const optionsList=Object.keys(this.customAutoCompletions).filter(key=>key.toLowerCase().startsWith(textBeforeCursor.toLowerCase()));
369
- return {
370
- from: context.pos - baseMatch[1].length,
371
- options: optionsList.map((key) => ({
372
- label: key,
373
- type: 'property',
374
- apply: key,
375
- boost: 999
376
- }))
377
- }
378
- }
379
- // Default to an empty list if no match
380
- return null;
381
- };
426
+ export default NileCodeEditor;
382
427
 
383
- resolveNestedProperties = (obj:any, keys:any[]) => {
384
- return keys.reduce((acc, key) => {
385
- if (acc && typeof acc === 'object') {
386
- return acc[key];
387
- }
388
- return null;
389
- }, obj);
390
- };
428
+ declare global {
429
+ interface HTMLElementTagNameMap {
430
+ 'nile-code-editor': NileCodeEditor;
431
+ }
432
+ }
391
433
 
392
- parsePath(text: string) {
393
- const regex = /([a-zA-Z_$][\w$]*)(\[(?:[^\]]+)\]|\.[a-zA-Z_$][\w$]*)*/g;
394
- const matches = [...text.matchAll(regex)];
395
- if (matches.length > 0) {
396
- const base = matches[0][1]; // The base object name
397
- const keys = [base];
398
- // Extract keys from dot or bracket notation
399
- const pathMatches = text.match(/\[(.*?)\]|\.(\w+)/g) || [];
400
- for (const match of pathMatches) {
401
- if (match.startsWith('[')) {
402
- keys.push(match.slice(1, -1).replace(/['"]/g, '')); // Remove brackets and quotes
403
- } else if (match.startsWith('.')) {
404
- keys.push(match.slice(1));
405
- }
434
+ /**
435
+ * Parses a string path into an array of keys representing nested object access
436
+ * @param text The path string to parse (e.g. "foo.bar[0].baz")
437
+ * @returns Array of keys if valid path, null otherwise
438
+ * @example
439
+ * parsePath("foo.bar[0]") // returns ["foo", "bar", "0"]
440
+ * parsePath("invalid") // returns null
441
+ */
442
+ function parsePath(text: string) {
443
+ const regex = /([a-zA-Z_$][\w$]*)(\[(?:[^\]]+)\]|\.[a-zA-Z_$][\w$]*)*/g;
444
+ const matches = [...text.matchAll(regex)];
445
+ if (matches.length > 0) {
446
+ const base = matches[0][1]; // The base object name
447
+ const keys = [base];
448
+ // Extract keys from dot or bracket notation
449
+ const pathMatches = text.match(/\[(.*?)\]|\.(\w+)/g) || [];
450
+ for (const match of pathMatches) {
451
+ if (match.startsWith('[')) {
452
+ keys.push(match.slice(1, -1).replace(/['"]/g, '')); // Remove brackets and quotes
453
+ } else if (match.startsWith('.')) {
454
+ keys.push(match.slice(1));
406
455
  }
407
- return keys;
408
456
  }
409
- return null;
410
- };
411
-
412
- isValidPath(path: string) {
413
- // Regex to validate the format of the string
414
- const regex = /^([a-zA-Z_$][\w$]*)(\.[a-zA-Z_$][\w$]*|\[\s*(['"]?[a-zA-Z0-9_$]*['"]?)\s*\])*([\.\[])?$/;
415
- // Test the string against the regex
416
- return regex.test(path);
457
+ return keys;
417
458
  }
459
+ return null;
460
+ };
418
461
 
419
- splitStringAtLastSeparator(input:string) {
420
- const lastSeparatorIndex = Math.max(input.lastIndexOf('.'), input.lastIndexOf('['));
421
- if (lastSeparatorIndex === -1) return [input, ''];
422
- return [input.slice(0, lastSeparatorIndex + 1), input.slice(lastSeparatorIndex + 1)];
423
- }
424
- /* #endregion */
462
+ /**
463
+ * Splits a path string at the last separator (. or [)
464
+ * @param input The path string to split
465
+ * @returns Array containing [path up to last separator, remainder after separator]
466
+ * @example
467
+ * splitStringAtLastSeparator("foo.bar[0]") // returns ["foo.bar[", "0"]
468
+ */
469
+ function splitStringAtLastSeparator(input:string) {
470
+ const lastSeparatorIndex = Math.max(input.lastIndexOf('.'), input.lastIndexOf('['));
471
+ if (lastSeparatorIndex === -1) return [input, ''];
472
+ return [input.slice(0, lastSeparatorIndex + 1), input.slice(lastSeparatorIndex + 1)];
425
473
  }
426
474
 
427
- export default NileCodeEditor;
475
+ /**
476
+ * Traverses an object using an array of keys to access nested properties
477
+ * @param obj The object to traverse
478
+ * @param keys Array of keys defining the path to the desired property
479
+ * @returns The value at the specified path, or null if path is invalid
480
+ * @example
481
+ * resolveNestedProperties({foo: {bar: 123}}, ["foo", "bar"]) // returns 123
482
+ */
483
+ function resolveNestedProperties (obj:any, keys:any[]){
484
+ return keys.reduce((acc, key) => {
485
+ if (acc && typeof acc === 'object') {
486
+ return acc[key];
487
+ }
488
+ return null;
489
+ }, obj);
490
+ };
428
491
 
429
- declare global {
430
- interface HTMLElementTagNameMap {
431
- 'nile-code-editor': NileCodeEditor;
432
- }
492
+ /**
493
+ * Validates if a string represents a valid object path format
494
+ * @param path The path string to validate
495
+ * @returns Boolean indicating if path format is valid
496
+ * @example
497
+ * isValidPath("foo.bar[0]") // returns true
498
+ * isValidPath("foo..bar") // returns false
499
+ */
500
+ function isValidPath(path: string) {
501
+ // Regex to validate the format of the string
502
+ const regex = /^([a-zA-Z_$][\w$]*)(\.[a-zA-Z_$][\w$]*|\[\s*(['"]?[a-zA-Z0-9_$]*['"]?)\s*\])*([\.\[])?$/;
503
+ // Test the string against the regex
504
+ return regex.test(path);
433
505
  }
506
+
507
+ /**
508
+ * Converts multi-line code into a single line by removing line breaks and extra whitespace
509
+ * @param code The code string to convert
510
+ * @returns Single line version of the code
511
+ * @example
512
+ * convertToSingleLine("foo\n bar") // returns "foo bar"
513
+ */
514
+ function convertToSingleLine(code: string) {
515
+ if (!code) return '';
516
+ // Remove line breaks and unnecessary whitespace
517
+ return code.replace(/\s+/g, ' ').trim();
518
+ }
@@ -125,7 +125,7 @@ export const styles = css`
125
125
  display: flex;
126
126
  flex-wrap: wrap;
127
127
  justify-content: end;
128
- gap: 0.25rem;
128
+ gap: 0.5rem;
129
129
  padding: 0 var(--header-spacing);
130
130
  }
131
131
 
@@ -58,8 +58,15 @@ export class NileFormHelpText extends LitElement {
58
58
  this.updateDisplayedText()
59
59
  }
60
60
 
61
- updateDisplayedText(){
62
- this.displayedText = this.isExpanded ? this.fullText : `${this.fullText.substring(0, this.textLimit)}...`;
61
+ updateDisplayedText() {
62
+ if (this.isExpanded) {
63
+ this.displayedText = this.fullText;
64
+ } else {
65
+ const validConcatNumber=concatSentence(this.fullText, this.textLimit)
66
+ const truncatedText = this.fullText.slice(0, validConcatNumber);
67
+ const ellipsis = this.fullText.length > validConcatNumber ? '...' : '';
68
+ this.displayedText = `${truncatedText}${ellipsis}`;
69
+ }
63
70
  }
64
71
 
65
72
  /* #endregion */
@@ -71,7 +78,7 @@ export class NileFormHelpText extends LitElement {
71
78
  * @slot This is a slot test
72
79
  */
73
80
  public render(): TemplateResult {
74
- const showMoreButton = this.fullText.length > this.textLimit+3;
81
+ const showMoreButton = this.fullText.length > this.textLimit;
75
82
  const iconName = this.isExpanded ? 'arrowup' : 'arrowdown';
76
83
 
77
84
  return html`
@@ -101,3 +108,30 @@ declare global {
101
108
  'nile-form-help-text': NileFormHelpText;
102
109
  }
103
110
  }
111
+
112
+ function concatSentence(sentence:string, n:number) {
113
+ if (n < 0 || n > sentence.length) {
114
+ throw new Error("Invalid value of n. It must be between 0 and the sentence length.");
115
+ }
116
+
117
+ // Adjust n if it falls in the middle of a word
118
+ if (sentence[n] !== " " && n !== 0 && n !== sentence.length) {
119
+ // Move left until the start of the word or the beginning of the sentence
120
+ let left = n;
121
+ while (left > 0 && sentence[left - 1] !== " ") {
122
+ left--;
123
+ }
124
+
125
+ // Move right until the end of the word or the end of the sentence
126
+ let right = n;
127
+ while (right < sentence.length && sentence[right] !== " ") {
128
+ right++;
129
+ }
130
+
131
+ // Adjust n to the closer boundary
132
+ n = (n - left <= right - n) ? left : right;
133
+ }
134
+
135
+ // Return the substring from the start to the adjusted n
136
+ return n;
137
+ }
@@ -726,7 +726,7 @@
726
726
  },
727
727
  {
728
728
  "name": "nile-code-editor",
729
- "description": "Nile icon component.\n\nEvents:\n\n * `nile-focus` {`Event`} - \n\n * `nile-blur` {`Event`} - \n\nAttributes:\n\n * `value` {`string`} - \n\n * `expandIcon` {`string`} - \n\n * `placeholder` {`string`} - \n\n * `customAutoCompletions` - \n\n * `language` {`\"javascript\" | \"sql\" | \"json\"`} - \n\n * `error-message` {`string`} - \n\n * `error` {`boolean`} - \n\n * `noborder` {`boolean`} - \n\n * `multiline` {`boolean`} - \n\n * `allowVariableInCustomSuggestion` {`boolean`} - \n\n * `lineNumbers` {`boolean`} - \n\n * `lineNumbersMultiline` {`boolean`} - \n\n * `hasScroller` {`boolean`} - \n\n * `expandable` {`boolean`} - \n\n * `readonly` {`boolean`} - \n\n * `debounce` {`boolean`} - \n\nProperties:\n\n * `codeEditor` {`HTMLInputElement`} - \n\n * `value` {`string`} - \n\n * `expandIcon` {`string`} - \n\n * `placeholder` {`string`} - \n\n * `customAutoCompletions` - \n\n * `language` {`\"javascript\" | \"sql\" | \"json\"`} - \n\n * `errorMessage` {`string`} - \n\n * `error` {`boolean`} - \n\n * `noborder` {`boolean`} - \n\n * `multiline` {`boolean`} - \n\n * `allowVariableInCustomSuggestion` {`boolean`} - \n\n * `lineNumbers` {`boolean`} - \n\n * `lineNumbersMultiline` {`boolean`} - \n\n * `hasScroller` {`boolean`} - \n\n * `expandable` {`boolean`} - \n\n * `readonly` {`boolean`} - \n\n * `debounce` {`boolean`} - \n\n * `view` - \n\n * `viewState` - \n\n * `timeOut` - \n\n * `lineNumbersComp` - \n\n * `restrictSingleLineComp` - \n\n * `readOnlyComp` - \n\n * `customCompletionComp` - \n\n * `placeholderComp` - \n\n * `insertBetweenCode` - \n\n * `customAutocomplete` - \n\n * `resolveNestedProperties` - \n\n * `BUBBLES` {`boolean`} - \n\n * `COMPOSED` {`boolean`} - \n\n * `CANCELABLE` {`boolean`} - ",
729
+ "description": "Nile icon component.\n\nEvents:\n\n * `nile-focus` {`Event`} - \n\n * `nile-blur` {`Event`} - \n\nAttributes:\n\n * `value` {`string`} - \n\n * `expandIcon` {`string`} - \n\n * `placeholder` {`string`} - \n\n * `customAutoCompletions` - \n\n * `customCompletionsPaths` {`string[]`} - \n\n * `language` {`\"html\" | \"javascript\" | \"sql\" | \"json\"`} - \n\n * `error-message` {`string`} - \n\n * `error` {`boolean`} - \n\n * `noborder` {`boolean`} - \n\n * `multiline` {`boolean`} - \n\n * `allowVariableInCustomSuggestion` {`boolean`} - \n\n * `lineNumbers` {`boolean`} - \n\n * `lineNumbersMultiline` {`boolean`} - \n\n * `hasScroller` {`boolean`} - \n\n * `expandable` {`boolean`} - \n\n * `readonly` {`boolean`} - \n\n * `debounce` {`boolean`} - \n\n * `debounceTimeout` {`number`} - \n\nProperties:\n\n * `codeEditor` {`HTMLInputElement`} - \n\n * `value` {`string`} - \n\n * `expandIcon` {`string`} - \n\n * `placeholder` {`string`} - \n\n * `customAutoCompletions` - \n\n * `customCompletionsPaths` {`string[]`} - \n\n * `language` {`\"html\" | \"javascript\" | \"sql\" | \"json\"`} - \n\n * `errorMessage` {`string`} - \n\n * `error` {`boolean`} - \n\n * `noborder` {`boolean`} - \n\n * `multiline` {`boolean`} - \n\n * `allowVariableInCustomSuggestion` {`boolean`} - \n\n * `lineNumbers` {`boolean`} - \n\n * `lineNumbersMultiline` {`boolean`} - \n\n * `hasScroller` {`boolean`} - \n\n * `expandable` {`boolean`} - \n\n * `readonly` {`boolean`} - \n\n * `debounce` {`boolean`} - \n\n * `debounceTimeout` {`number`} - \n\n * `view` - \n\n * `viewState` - \n\n * `timeOut` - \n\n * `lineNumbersComp` - \n\n * `restrictSingleLineComp` - \n\n * `readOnlyComp` - \n\n * `customCompletionComp` - \n\n * `placeholderComp` - \n\n * `customAutocomplete` - Custom autocomplete handler for code editor suggestions\n\n * `insertBetweenCode` - \n\n * `BUBBLES` {`boolean`} - \n\n * `COMPOSED` {`boolean`} - \n\n * `CANCELABLE` {`boolean`} - ",
730
730
  "attributes": [
731
731
  {
732
732
  "name": "value",
@@ -744,10 +744,17 @@
744
744
  "name": "customAutoCompletions",
745
745
  "description": "`customAutoCompletions` - \n\nProperty: customAutoCompletions\n\nDefault: [object Object]"
746
746
  },
747
+ {
748
+ "name": "customCompletionsPaths",
749
+ "description": "`customCompletionsPaths` {`string[]`} - \n\nProperty: customCompletionsPaths\n\nDefault: "
750
+ },
747
751
  {
748
752
  "name": "language",
749
- "description": "`language` {`\"javascript\" | \"sql\" | \"json\"`} - \n\nProperty: language\n\nDefault: javascript",
753
+ "description": "`language` {`\"html\" | \"javascript\" | \"sql\" | \"json\"`} - \n\nProperty: language\n\nDefault: javascript",
750
754
  "values": [
755
+ {
756
+ "name": "html"
757
+ },
751
758
  {
752
759
  "name": "javascript"
753
760
  },
@@ -813,6 +820,10 @@
813
820
  "description": "`debounce` {`boolean`} - \n\nProperty: debounce\n\nDefault: false",
814
821
  "valueSet": "v"
815
822
  },
823
+ {
824
+ "name": "debounceTimeout",
825
+ "description": "`debounceTimeout` {`number`} - \n\nProperty: debounceTimeout\n\nDefault: 200"
826
+ },
816
827
  {
817
828
  "name": "onnile-focus",
818
829
  "description": "`nile-focus` {`Event`} - "