lexgui 0.7.12 → 0.7.13

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.
@@ -946,8 +946,11 @@ class CodeEditor
946
946
  {
947
947
  this._processSelection( cursor, e );
948
948
  }
949
- } else if( !e.keepSelection )
949
+ }
950
+ else if( !e.keepSelection )
951
+ {
950
952
  this.endSelection();
953
+ }
951
954
  });
952
955
 
953
956
  this.action( 'End', false, ( ln, cursor, e ) => {
@@ -2502,7 +2505,7 @@ class CodeEditor
2502
2505
  {
2503
2506
  // Make sure we only keep the main cursor..
2504
2507
  this._removeSecondaryCursors();
2505
- this.cursorToLine( cursor, ln, true );
2508
+ this.cursorToLine( cursor, ln );
2506
2509
  this.cursorToPosition( cursor, string.length );
2507
2510
  }
2508
2511
 
@@ -4077,7 +4080,7 @@ class CodeEditor
4077
4080
  charCounter += t.length;
4078
4081
  };
4079
4082
 
4080
- let iter = lineString.matchAll(/(<!--|-->|\*\/|\/\*|::|[\[\](){}<>.,;:*"'%@$!/= ])/g);
4083
+ let iter = lineString.matchAll(/(<!--|-->|\*\/|\/\*|::|[\[\](){}<>.,;:*"'`%@$!/= ])/g);
4081
4084
  let subtokens = iter.next();
4082
4085
  if( subtokens.value )
4083
4086
  {
@@ -4237,13 +4240,29 @@ class CodeEditor
4237
4240
  usePreviousTokenToCheckString = true;
4238
4241
  customStringKeys['@['] = ']';
4239
4242
  }
4243
+ else if( highlight == 'javascript' || highlight == 'typescript' )
4244
+ {
4245
+ customStringKeys["@`"] = "`";
4246
+ }
4240
4247
 
4241
4248
  // Manage strings
4242
4249
  this._stringEnded = false;
4243
4250
 
4244
4251
  if( usePreviousTokenToCheckString || ( !inBlockComment && ( lang.tags ?? false ? ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) ) : true ) ) )
4245
4252
  {
4246
- const _checkIfStringEnded = t => {
4253
+ const _checkIfStringEnded = ( t ) => {
4254
+
4255
+ if( this._stringInterpolation )
4256
+ {
4257
+ if( token == "$" && next == "{" )
4258
+ {
4259
+ delete this._stringInterpolation;
4260
+ this._stringInterpolationOpened = true;
4261
+ this._stringEnded = true;
4262
+ return;
4263
+ }
4264
+ }
4265
+
4247
4266
  const idx = Object.values( customStringKeys ).indexOf( t );
4248
4267
  this._stringEnded = (idx > -1) && (idx == Object.values(customStringKeys).indexOf( customStringKeys[ '@' + this._buildingString ] ));
4249
4268
  };
@@ -4257,12 +4276,23 @@ class CodeEditor
4257
4276
  // Start new string
4258
4277
  this._buildingString = ( usePreviousTokenToCheckString ? ctxData.prevWithSpaces : token );
4259
4278
 
4279
+ if( ( highlight == 'javascript' || highlight == 'typescript' ) && token == "`" )
4280
+ {
4281
+ this._stringInterpolation = true;
4282
+ }
4283
+
4260
4284
  // Check if string ended in same token using next...
4261
4285
  if( usePreviousTokenToCheckString )
4262
4286
  {
4263
4287
  _checkIfStringEnded( ctxData.nextWithSpaces );
4264
4288
  }
4265
4289
  }
4290
+ else if( this._stringInterpolationOpened && prev == "}" )
4291
+ {
4292
+ delete this._stringInterpolationOpened;
4293
+ this._stringInterpolation = true;
4294
+ this._buildingString = "`";
4295
+ }
4266
4296
  }
4267
4297
 
4268
4298
  // Update context data for next tests
@@ -4284,6 +4314,16 @@ class CodeEditor
4284
4314
  // Get highlighting class based on language common and specific rules
4285
4315
  let tokenClass = this._getTokenHighlighting( ctxData, highlight );
4286
4316
 
4317
+ if( this._stringInterpolationOpened && this._pendingString )
4318
+ {
4319
+ this._pendingString = this._pendingString.substring( 0, this._pendingString.indexOf( "$" ) );
4320
+
4321
+ if( ctxData.tokens[ tokenIndex + 1 ] == "{" )
4322
+ {
4323
+ ctxData.tokens[ tokenIndex + 1 ] = "${";
4324
+ }
4325
+ }
4326
+
4287
4327
  // We finished constructing a string
4288
4328
  if( this._buildingString && ( this._stringEnded || isLastToken ) && !inBlockComment )
4289
4329
  {
@@ -4671,43 +4711,57 @@ class CodeEditor
4671
4711
  this.hideAutoCompleteBox();
4672
4712
  }
4673
4713
 
4674
- cursorToRight( key, cursor )
4714
+ cursorToRight( text, cursor )
4675
4715
  {
4676
- if( !key ) return;
4716
+ if( !text || !text.length ) return;
4717
+
4718
+ const chars = text.length;
4719
+ const offset = chars * this.charWidth;
4677
4720
 
4678
- cursor._left += this.charWidth;
4721
+ cursor._left += offset;
4679
4722
  cursor.style.left = `calc( ${ cursor._left }px + ${ this.xPadding } )`;
4680
- cursor.position++;
4723
+ cursor.position += chars;
4681
4724
 
4682
4725
  this.restartBlink();
4683
4726
 
4684
4727
  // Add horizontal scroll
4728
+ const rightMargin = this.charWidth;
4729
+ const cursorX = ( cursor.position * this.charWidth );
4685
4730
  const currentScrollLeft = this.getScrollLeft();
4686
- var viewportSizeX = ( this.codeScroller.clientWidth + currentScrollLeft ) - CodeEditor.LINE_GUTTER_WIDTH; // Gutter offset
4687
- if( (cursor.position * this.charWidth) >= viewportSizeX )
4731
+ const viewportSizeX = this.codeScroller.clientWidth - CodeEditor.LINE_GUTTER_WIDTH; // Gutter offset
4732
+ const viewportX = viewportSizeX + currentScrollLeft;
4733
+
4734
+ if( cursorX >= ( viewportX - rightMargin ) )
4688
4735
  {
4689
- this.setScrollLeft( currentScrollLeft + this.charWidth );
4736
+ const scroll = Math.max( cursorX - ( viewportSizeX - rightMargin ), 0 );
4737
+ this.setScrollLeft( scroll );
4690
4738
  }
4691
4739
  }
4692
4740
 
4693
- cursorToLeft( key, cursor )
4741
+ cursorToLeft( text, cursor )
4694
4742
  {
4695
- if( !key ) return;
4743
+ if( !text || !text.length ) return;
4744
+
4745
+ const chars = text.length;
4746
+ const offset = chars * this.charWidth;
4696
4747
 
4697
- cursor._left -= this.charWidth;
4748
+ cursor._left -= offset;
4698
4749
  cursor._left = Math.max( cursor._left, 0 );
4699
4750
  cursor.style.left = `calc( ${ cursor._left }px + ${ this.xPadding } )`;
4700
- cursor.position--;
4751
+ cursor.position -= chars;
4701
4752
  cursor.position = Math.max( cursor.position, 0 );
4753
+
4702
4754
  this.restartBlink();
4703
4755
 
4704
4756
  // Add horizontal scroll
4705
-
4757
+ const leftMargin = this.charWidth;
4758
+ const cursorX = ( cursor.position * this.charWidth );
4706
4759
  const currentScrollLeft = this.getScrollLeft();
4707
- var viewportSizeX = currentScrollLeft; // Gutter offset
4708
- if( ( ( cursor.position - 1 ) * this.charWidth ) < viewportSizeX )
4760
+
4761
+ if( cursorX < ( currentScrollLeft + leftMargin ) )
4709
4762
  {
4710
- this.setScrollLeft( currentScrollLeft - this.charWidth );
4763
+ const scroll = Math.max( cursorX - leftMargin, 0 );
4764
+ this.setScrollLeft( scroll );
4711
4765
  }
4712
4766
  }
4713
4767
 
@@ -4759,9 +4813,13 @@ class CodeEditor
4759
4813
  return;
4760
4814
  }
4761
4815
 
4762
- for( let char of text )
4816
+ if( reverse )
4763
4817
  {
4764
- reverse ? this.cursorToLeft( char, cursor ) : this.cursorToRight( char, cursor );
4818
+ this.cursorToLeft( text, cursor )
4819
+ }
4820
+ else
4821
+ {
4822
+ this.cursorToRight( text, cursor );
4765
4823
  }
4766
4824
  }
4767
4825
 
@@ -4867,15 +4925,17 @@ class CodeEditor
4867
4925
  if( flag & CodeEditor.CURSOR_LEFT )
4868
4926
  {
4869
4927
  cursor._left = 0;
4870
- cursor.style.left = "calc(" + ( -this.getScrollLeft() ) + "px + " + this.xPadding + ")";
4928
+ cursor.style.left = "calc(" + this.xPadding + ")";
4871
4929
  cursor.position = 0;
4930
+ this.setScrollLeft( 0 );
4872
4931
  }
4873
4932
 
4874
4933
  if( flag & CodeEditor.CURSOR_TOP )
4875
4934
  {
4876
4935
  cursor._top = 0;
4877
- cursor.style.top = ( -this.getScrollTop() ) + "px";
4936
+ cursor.style.top = "0px";
4878
4937
  cursor.line = 0;
4938
+ this.setScrollTop( 0 )
4879
4939
  }
4880
4940
  }
4881
4941
 
@@ -5034,7 +5094,7 @@ class CodeEditor
5034
5094
  LX.doAsync( () => {
5035
5095
  this.codeScroller.scrollLeft = value;
5036
5096
  this.setScrollBarValue( 'horizontal', 0 );
5037
- }, 20 );
5097
+ }, 10 );
5038
5098
  }
5039
5099
 
5040
5100
  setScrollTop( value )
@@ -5043,7 +5103,7 @@ class CodeEditor
5043
5103
  LX.doAsync( () => {
5044
5104
  this.codeScroller.scrollTop = value;
5045
5105
  this.setScrollBarValue( 'vertical' );
5046
- }, 20 );
5106
+ }, 10 );
5047
5107
  }
5048
5108
 
5049
5109
  resize( flag = CodeEditor.RESIZE_SCROLLBAR_H_V, pMaxLength, onResize )
package/build/lexgui.js CHANGED
@@ -14,7 +14,7 @@ console.warn( 'Script _build/lexgui.js_ is depracated and will be removed soon.
14
14
  */
15
15
 
16
16
  const LX = {
17
- version: "0.7.12",
17
+ version: "0.7.13",
18
18
  ready: false,
19
19
  extensions: [], // Store extensions used
20
20
  signals: {}, // Events and triggers
@@ -5329,11 +5329,12 @@ LX.guidGenerator = guidGenerator;
5329
5329
  function buildTextPattern( options = {} )
5330
5330
  {
5331
5331
  let patterns = [];
5332
- if ( options.lowercase ) patterns.push("(?=.*[a-z])");
5333
- if ( options.uppercase ) patterns.push("(?=.*[A-Z])");
5334
- if ( options.digit ) patterns.push("(?=.*\\d)");
5335
- if ( options.specialChar ) patterns.push("(?=.*[@#$%^&+=!])");
5336
- if ( options.noSpaces ) patterns.push("(?!.*\\s)");
5332
+ if( options.lowercase ) patterns.push("(?=.*[a-z])");
5333
+ if( options.uppercase ) patterns.push("(?=.*[A-Z])");
5334
+ if( options.digit ) patterns.push("(?=.*\\d)");
5335
+ if( options.specialChar ) patterns.push("(?=.*[@#$%^&+=!])");
5336
+ if( options.noSpaces ) patterns.push("(?!.*\\s)");
5337
+ if( options.email ) patterns.push("(^[^\s@]+@[^\s@]+\.[^\s@]+$)");
5337
5338
 
5338
5339
  let minLength = options.minLength || 0;
5339
5340
  let maxLength = options.maxLength || ""; // Empty means no max length restriction
@@ -5344,6 +5345,45 @@ function buildTextPattern( options = {} )
5344
5345
 
5345
5346
  LX.buildTextPattern = buildTextPattern;
5346
5347
 
5348
+ /**
5349
+ * Checks a value against a set of pattern requirements and returns an array
5350
+ * of specific error messages for all criteria that failed.
5351
+ * @param { String } value The string to validate.
5352
+ * @param { Object } pattern The pattern options
5353
+ * @returns { Array } An array of error messages for failed criteria.
5354
+ */
5355
+ function validateValueAtPattern( value, pattern = {}, ...args )
5356
+ {
5357
+ const errors = [];
5358
+ const minLength = pattern.minLength || 0;
5359
+ const maxLength = pattern.maxLength; // undefined means no max limit
5360
+
5361
+ // Length requirements
5362
+ if( value.length < minLength ) errors.push(`Must be at least ${ minLength } characters long.`);
5363
+ else if( maxLength !== undefined && value.length > maxLength ) errors.push(`Must be no more than ${ maxLength } characters long.`);
5364
+
5365
+ // Check for Lowercase, Uppercase, Digits
5366
+ if( pattern.lowercase && !/[a-z]/.test( value ) ) errors.push( "Must contain at least one lowercase letter (a-z)." );
5367
+ if( pattern.uppercase && !/[A-Z]/.test( value ) ) errors.push( "Must contain at least one uppercase letter (A-Z)." );
5368
+ if( pattern.digit && !/\d/.test( value ) ) errors.push( "Must contain at least one number (0-9)." );
5369
+
5370
+ // Check for No Spaces (The original regex was (?!.*\s), meaning 'not followed by any character and a space')
5371
+ if( pattern.noSpaces && /\s/.test(value)) errors.push("Must NOT contain any spaces.");
5372
+
5373
+ // Check for Special Character (using the same set as buildTextPattern)
5374
+ if( pattern.specialChar && !/[@#$%^&+=!]/.test( value ) ) errors.push("Must contain at least one special character (e.g., @, #, $, %, ^, &, +, =, !).");
5375
+
5376
+ // Check email formatting
5377
+ if( pattern.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test( value ) ) errors.push("Must have a valid email format.");
5378
+
5379
+ // Check match to any other text word
5380
+ if( pattern.fieldMatchName && value !== ( args[ 0 ] ) ) errors.push(`Must match ${ pattern.fieldMatchName } field.`);
5381
+
5382
+ return errors;
5383
+ }
5384
+
5385
+ LX.validateValueAtPattern = validateValueAtPattern;
5386
+
5347
5387
  /**
5348
5388
  * @method makeDraggable
5349
5389
  * @description Allows an element to be dragged
@@ -9062,7 +9102,14 @@ class TextInput extends BaseComponent
9062
9102
 
9063
9103
  this.onSetValue = ( newValue, skipCallback, event ) => {
9064
9104
 
9065
- if( !this.valid( newValue ) || ( this._lastValueTriggered == newValue ) )
9105
+ let skipTrigger = ( this._lastValueTriggered == newValue );
9106
+
9107
+ if( !options.ignoreValidation )
9108
+ {
9109
+ skipTrigger |= ( !this.valid( newValue ) );
9110
+ }
9111
+
9112
+ if( skipTrigger )
9066
9113
  {
9067
9114
  return;
9068
9115
  }
@@ -9082,11 +9129,11 @@ class TextInput extends BaseComponent
9082
9129
  container.style.width = options.inputWidth ?? `calc( 100% - ${ realNameWidth })`;
9083
9130
  };
9084
9131
 
9085
- this.valid = ( v ) => {
9132
+ this.valid = ( v, matchField ) => {
9086
9133
  v = v ?? this.value();
9087
- if( ( wValue.pattern ?? "" ) == "" ) return true;
9088
- const regexp = new RegExp( wValue.pattern );
9089
- return regexp.test( v );
9134
+ if( !options.pattern ) return true;
9135
+ const errs = LX.validateValueAtPattern( v, options.pattern, matchField );
9136
+ return ( errs.length == 0 );
9090
9137
  };
9091
9138
 
9092
9139
  let container = document.createElement( 'div' );
@@ -9115,7 +9162,7 @@ class TextInput extends BaseComponent
9115
9162
 
9116
9163
  if( options.pattern )
9117
9164
  {
9118
- wValue.setAttribute( "pattern", options.pattern );
9165
+ wValue.setAttribute( "pattern", LX.buildTextPattern( options.pattern ) );
9119
9166
  }
9120
9167
 
9121
9168
  const trigger = options.trigger ?? "default";
@@ -9785,13 +9832,14 @@ class Form extends BaseComponent
9785
9832
 
9786
9833
  if( entryData.constructor != Object )
9787
9834
  {
9788
- const oldValue = JSON.parse( JSON.stringify( entryData ) );
9835
+ const oldValue = LX.deepCopy( entryData );
9789
9836
  entryData = { value: oldValue };
9790
9837
  data[ entry ] = entryData;
9791
9838
  }
9792
9839
 
9793
- entryData.placeholder = entryData.placeholder ?? ( entryData.label ?? `Enter ${ entry }` );
9794
9840
  entryData.width = "100%";
9841
+ entryData.placeholder = entryData.placeholder ?? ( entryData.label ?? `Enter ${ entry }` );
9842
+ entryData.ignoreValidation = true;
9795
9843
 
9796
9844
  if( !( options.skipLabels ?? false ) )
9797
9845
  {
@@ -9807,14 +9855,14 @@ class Form extends BaseComponent
9807
9855
  container.formData[ entry ] = entryData.constructor == Object ? entryData.value : entryData;
9808
9856
  }
9809
9857
 
9810
- const buttonContainer = LX.makeContainer( ["100%", "auto"], "flex flex-row", "", container );
9858
+ const buttonContainer = LX.makeContainer( ["100%", "auto"], "flex flex-row mt-2", "", container );
9811
9859
 
9812
9860
  if( options.secondaryActionName || options.secondaryActionCallback )
9813
9861
  {
9814
9862
  const secondaryButton = new LX.Button( null, options.secondaryActionName ?? "Cancel", ( value, event ) => {
9815
- if( callback )
9863
+ if( options.secondaryActionCallback )
9816
9864
  {
9817
- callback( container.formData, event );
9865
+ options.secondaryActionCallback( container.formData, event );
9818
9866
  }
9819
9867
  }, { width: "100%", minWidth: "0", buttonClass: options.secondaryButtonClass ?? "primary" } );
9820
9868
 
@@ -9829,9 +9877,22 @@ class Form extends BaseComponent
9829
9877
  {
9830
9878
  let entryData = data[ entry ];
9831
9879
 
9832
- if( !entryData.textComponent.valid() )
9880
+ const pattern = entryData.pattern;
9881
+ const matchField = pattern?.fieldMatchName ? container.formData[ pattern.fieldMatchName ] : undefined;
9882
+
9883
+ if( !entryData.textComponent.valid( undefined, matchField ) )
9833
9884
  {
9834
- errors.push( { type: "input_not_valid", entry } );
9885
+ const err = { entry, type: "input_not_valid" };
9886
+ err.messages = [];
9887
+ if( pattern )
9888
+ {
9889
+ err.messages = LX.validateValueAtPattern(
9890
+ container.formData[ entry ],
9891
+ pattern,
9892
+ matchField
9893
+ );
9894
+ }
9895
+ errors.push( err );
9835
9896
  }
9836
9897
  }
9837
9898