lexgui 8.3.2 → 8.4.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.
@@ -16,7 +16,7 @@
16
16
  exports.LX = g$2.LX;
17
17
  if (!exports.LX) {
18
18
  exports.LX = {
19
- version: '8.3.2',
19
+ version: '8.4.1',
20
20
  ready: false,
21
21
  extensions: [], // Store extensions used
22
22
  extraCommandbarEntries: [], // User specific entries for command bar
@@ -436,6 +436,8 @@
436
436
  ComponentType[ComponentType["LABEL"] = 39] = "LABEL";
437
437
  ComponentType[ComponentType["BLANK"] = 40] = "BLANK";
438
438
  ComponentType[ComponentType["RATE"] = 41] = "RATE";
439
+ ComponentType[ComponentType["EMPTY"] = 42] = "EMPTY";
440
+ ComponentType[ComponentType["DESCRIPTION"] = 43] = "DESCRIPTION";
439
441
  })(exports.ComponentType || (exports.ComponentType = {}));
440
442
  exports.LX.ComponentType = exports.ComponentType;
441
443
  /**
@@ -737,6 +739,9 @@
737
739
  const realNameWidth = this.root.domName?.style.width ?? '0px';
738
740
  wValue.style.width = `calc( 100% - ${realNameWidth})`;
739
741
  };
742
+ this.onSetDisabled = (disabled) => {
743
+ wValue.disabled = disabled;
744
+ };
740
745
  // In case of swap, set if a change has to be performed
741
746
  this.setState = function (v, skipCallback) {
742
747
  const swapInput = wValue.querySelector('input');
@@ -1474,6 +1479,12 @@
1474
1479
  const realNameWidth = this.root.domName?.style.width ?? '0px';
1475
1480
  container.style.width = options.inputWidth ?? `calc( 100% - ${realNameWidth})`;
1476
1481
  };
1482
+ this.onSetDisabled = (disabled) => {
1483
+ vecinput.disabled = disabled;
1484
+ const slider = this.root.querySelector('input[type="range"]');
1485
+ if (slider)
1486
+ slider.disabled = disabled;
1487
+ };
1477
1488
  this.setLimits = (newMin, newMax, newStep) => { };
1478
1489
  var container = document.createElement('div');
1479
1490
  container.className = 'lexnumber';
@@ -1504,7 +1515,7 @@
1504
1515
  if (!options.skipSlider && options.min !== undefined && options.max !== undefined) {
1505
1516
  let sliderBox = exports.LX.makeContainer(['100%', 'auto'], 'z-1 input-box', '', box);
1506
1517
  let slider = document.createElement('input');
1507
- slider.className = 'lexinputslider';
1518
+ slider.className = 'lexinputslider disabled:pointer-events-none disabled:opacity-50';
1508
1519
  slider.min = options.min;
1509
1520
  slider.max = options.max;
1510
1521
  slider.step = options.step ?? 1;
@@ -1641,6 +1652,11 @@
1641
1652
  const realNameWidth = this.root.domName?.style.width ?? '0px';
1642
1653
  container.style.width = options.inputWidth ?? `calc( 100% - ${realNameWidth})`;
1643
1654
  };
1655
+ this.onSetDisabled = (disabled) => {
1656
+ const input = this.root.querySelector('input');
1657
+ if (input)
1658
+ input.disabled = disabled;
1659
+ };
1644
1660
  this.valid = (v, matchField) => {
1645
1661
  v = v ?? this.value();
1646
1662
  if (!options.pattern)
@@ -1800,6 +1816,9 @@
1800
1816
  const realNameWidth = this.root.domName?.style.width ?? '0px';
1801
1817
  container.style.width = options.inputWidth ?? `calc( 100% - ${realNameWidth})`;
1802
1818
  };
1819
+ this.onSetDisabled = (disabled) => {
1820
+ selectedOption?.setDisabled(disabled);
1821
+ };
1803
1822
  let container = document.createElement('div');
1804
1823
  container.className = 'lexselect';
1805
1824
  this.root.appendChild(container);
@@ -2074,6 +2093,13 @@
2074
2093
  this._trigger(new IEvent$1(name, values, event), callback);
2075
2094
  }
2076
2095
  };
2096
+ this.onSetDisabled = (disabled) => {
2097
+ if (this.root.dataset['opened'] == 'true' && disabled) {
2098
+ this.root.dataset['opened'] = false;
2099
+ this.root.querySelector('.lexarrayitems').toggleAttribute('hidden', true);
2100
+ }
2101
+ toggleButton.setDisabled(disabled);
2102
+ };
2077
2103
  // Add open array button
2078
2104
  let container = document.createElement('div');
2079
2105
  container.className = 'lexarray shrink-1 grow-1 ml-4';
@@ -2165,9 +2191,10 @@
2165
2191
  const container = exports.LX.makeContainer(['100%', 'auto'], 'lexcard max-w-sm flex flex-col gap-4 bg-card border-color rounded-xl py-6', '', this.root);
2166
2192
  if (options.header) {
2167
2193
  const hasAction = options.header.action !== undefined;
2194
+ const actionButtonOptions = options.header.action.options ?? {};
2168
2195
  let header = exports.LX.makeContainer(['100%', 'auto'], `flex ${hasAction ? 'flex-row gap-4' : 'flex-col gap-1'} px-6`, '', container);
2169
2196
  if (hasAction) {
2170
- const actionBtn = new Button(null, options.header.action.name, options.header.action.callback, { buttonClass: 'secondary' });
2197
+ const actionBtn = new Button(null, options.header.action.name, options.header.action.callback, { buttonClass: 'secondary', ...actionButtonOptions });
2171
2198
  header.appendChild(actionBtn.root);
2172
2199
  const titleDescBox = exports.LX.makeContainer(['75%', 'auto'], `flex flex-col gap-1`, '');
2173
2200
  header.prepend(titleDescBox);
@@ -2234,6 +2261,9 @@
2234
2261
  const realNameWidth = this.root.domName?.style.width ?? '0px';
2235
2262
  container.style.width = options.inputWidth ?? `calc( 100% - ${realNameWidth})`;
2236
2263
  };
2264
+ this.onSetDisabled = (disabled) => {
2265
+ checkbox.disabled = disabled;
2266
+ };
2237
2267
  let container = document.createElement('div');
2238
2268
  container.className = 'flex items-center gap-2 my-0 mx-auto [&_span]:truncate [&_span]:flex-auto-fill';
2239
2269
  this.root.appendChild(container);
@@ -2862,7 +2892,12 @@
2862
2892
  const realNameWidth = this.root.domName?.style.width ?? '0px';
2863
2893
  container.style.width = `calc( 100% - ${realNameWidth})`;
2864
2894
  };
2865
- var container = document.createElement('span');
2895
+ this.onSetDisabled = (disabled) => {
2896
+ textComponent.setDisabled(disabled);
2897
+ sampleContainer.classList.toggle('pointer-events-none', disabled);
2898
+ sampleContainer.classList.toggle('opacity-50', disabled);
2899
+ };
2900
+ let container = document.createElement('span');
2866
2901
  container.className = 'lexcolor';
2867
2902
  this.root.appendChild(container);
2868
2903
  this.picker = new ColorPicker(value, {
@@ -3026,6 +3061,11 @@
3026
3061
  this._trigger(new IEvent$1(name, newValue, event), callback);
3027
3062
  }
3028
3063
  };
3064
+ this.onSetDisabled = (disabled) => {
3065
+ substrButton.setDisabled(disabled);
3066
+ addButton.setDisabled(disabled);
3067
+ input.disabled = disabled;
3068
+ };
3029
3069
  this.count = value;
3030
3070
  const min = options.min ?? 0;
3031
3071
  const max = options.max ?? 100;
@@ -3771,6 +3811,12 @@
3771
3811
  const realNameWidth = this.root.domName?.style.width ?? '0px';
3772
3812
  container.style.width = `calc( 100% - ${realNameWidth})`;
3773
3813
  };
3814
+ this.onSetDisabled = (disabled) => {
3815
+ const buttons = this.root.querySelectorAll('button');
3816
+ buttons.forEach((b) => {
3817
+ b.disabled = disabled;
3818
+ });
3819
+ };
3774
3820
  const container = exports.LX.makeContainer(['auto', 'auto'], 'lexdate flex flex-row');
3775
3821
  this.root.appendChild(container);
3776
3822
  if (!dateAsRange) {
@@ -4163,6 +4209,52 @@
4163
4209
  exports.LX.CanvasDial = CanvasDial;
4164
4210
  exports.LX.Dial = Dial;
4165
4211
 
4212
+ // Empty.ts @jxarco
4213
+ /**
4214
+ * @class Empty
4215
+ * @description Empty Component
4216
+ */
4217
+ class Empty extends BaseComponent$1 {
4218
+ constructor(name, options = {}) {
4219
+ options.hideName = true;
4220
+ super(exports.ComponentType.EMPTY, name, null, options);
4221
+ this.root.classList.add('place-content-center');
4222
+ const container = exports.LX.makeContainer(['100%', 'auto'], 'lexcard max-w-sm flex flex-col gap-4 bg-card border-color rounded-xl py-6', '', this.root);
4223
+ if (options.header) {
4224
+ let header = exports.LX.makeContainer(['100%', 'auto'], `flex flex-col gap-4 px-6 items-center`, '', container);
4225
+ if (options.header.icon) {
4226
+ const icon = exports.LX.makeIcon(options.header.icon, { iconClass: 'bg-secondary p-2 rounded-lg!', svgClass: 'lg' });
4227
+ header.appendChild(icon);
4228
+ }
4229
+ else if (options.header.avatar) {
4230
+ const avatar = new exports.LX.Avatar(options.header.avatar);
4231
+ header.appendChild(avatar.root);
4232
+ }
4233
+ if (options.header.title) {
4234
+ exports.LX.makeElement('div', 'text-center text-foreground leading-none font-medium', options.header.title, header);
4235
+ }
4236
+ if (options.header.description) {
4237
+ exports.LX.makeElement('div', 'text-sm text-center text-balance text-muted-foreground', options.header.description, header);
4238
+ }
4239
+ }
4240
+ if (options.actions) {
4241
+ const content = exports.LX.makeContainer(['100%', 'auto'], 'flex flex-row gap-1 px-6 justify-center', '', container);
4242
+ for (let a of options.actions) {
4243
+ const action = new exports.LX.Button(null, a.name, a.callback, { buttonClass: "sm outline", ...a.options });
4244
+ content.appendChild(action.root);
4245
+ }
4246
+ }
4247
+ if (options.footer) {
4248
+ const footer = exports.LX.makeContainer(['100%', 'auto'], 'flex flex-col gap-1 px-6', '', container);
4249
+ const elements = [].concat(options.footer);
4250
+ for (let e of elements) {
4251
+ footer.appendChild(e.root ? e.root : e);
4252
+ }
4253
+ }
4254
+ }
4255
+ }
4256
+ exports.LX.Empty = Empty;
4257
+
4166
4258
  // FileInput.ts @jxarco
4167
4259
  /**
4168
4260
  * @class FileInput
@@ -4178,6 +4270,9 @@
4178
4270
  const realNameWidth = this.root.domName?.style.width ?? '0px';
4179
4271
  input.style.width = `calc( 100% - ${realNameWidth})`;
4180
4272
  };
4273
+ this.onSetDisabled = (disabled) => {
4274
+ input.disabled = disabled;
4275
+ };
4181
4276
  // Create hidden input
4182
4277
  let input = document.createElement('input');
4183
4278
  input.className = 'lexfileinput';
@@ -4385,6 +4480,9 @@
4385
4480
  const realNameWidth = this.root.domName?.style.width ?? '0px';
4386
4481
  container.style.width = `calc( 100% - ${realNameWidth})`;
4387
4482
  };
4483
+ this.onSetDisabled = (disabled) => {
4484
+ this.setLayers(value);
4485
+ };
4388
4486
  const container = exports.LX.makeElement('div', 'lexlayers grid', '', this.root);
4389
4487
  const maxBits = options.maxBits ?? 16;
4390
4488
  this.setLayers = (val) => {
@@ -4461,6 +4559,9 @@
4461
4559
  const realNameWidth = this.root.domName?.style.width ?? '0px';
4462
4560
  listContainer.style.width = `calc( 100% - ${realNameWidth})`;
4463
4561
  };
4562
+ this.onSetDisabled = (disabled) => {
4563
+ this._updateValues(values);
4564
+ };
4464
4565
  this._updateValues = (newValues) => {
4465
4566
  values = newValues;
4466
4567
  listContainer.innerHTML = '';
@@ -4928,16 +5029,19 @@
4928
5029
  const realNameWidth = this.root.domName?.style.width ?? '0px';
4929
5030
  container.style.width = `calc( 100% - ${realNameWidth})`;
4930
5031
  };
5032
+ this.onSetDisabled = (disabled) => {
5033
+ openerButton.setDisabled(disabled);
5034
+ };
4931
5035
  var container = document.createElement('div');
4932
5036
  container.className = 'lexmap2d';
4933
5037
  this.root.appendChild(container);
4934
5038
  this.map2d = new CanvasMap2D(points, callback, options);
4935
- const calendarIcon = exports.LX.makeIcon(options.mapIcon ?? 'SquareMousePointer');
4936
- const calendarButton = new Button(null, 'Open Map', () => {
4937
- this._popover = new Popover(calendarButton.root, [this.map2d]);
5039
+ const icon = exports.LX.makeIcon(options.mapIcon ?? 'SquareMousePointer');
5040
+ const openerButton = new Button(null, 'Open Map', () => {
5041
+ this._popover = new Popover(openerButton.root, [this.map2d]);
4938
5042
  }, { buttonClass: `outline justify-between`, disabled: this.disabled });
4939
- calendarButton.root.querySelector('button').appendChild(calendarIcon);
4940
- container.appendChild(calendarButton.root);
5043
+ openerButton.root.querySelector('button').appendChild(icon);
5044
+ container.appendChild(openerButton.root);
4941
5045
  exports.LX.doAsync(this.onResize.bind(this));
4942
5046
  }
4943
5047
  }
@@ -5644,6 +5748,9 @@
5644
5748
  const realNameWidth = this.root.domName?.style.width ?? '0px';
5645
5749
  container.style.width = `calc( 100% - ${realNameWidth})`;
5646
5750
  };
5751
+ this.onSetDisabled = (disabled) => {
5752
+ _refreshInput(value);
5753
+ };
5647
5754
  const container = document.createElement('div');
5648
5755
  container.className = 'lexotp flex flex-row items-center';
5649
5756
  this.root.appendChild(container);
@@ -5657,7 +5764,7 @@
5657
5764
  for (let j = 0; j < g.length; ++j) {
5658
5765
  let number = valueString[itemsCount++];
5659
5766
  number = number == 'x' ? '' : number;
5660
- const slotDom = exports.LX.makeContainer(['36px', '30px'], 'lexotpslot border-t-color border-b-color border-l-color px-3 cursor-text select-none font-medium outline-none', number, container);
5767
+ const slotDom = exports.LX.makeContainer(['36px', '30px'], 'lexotpslot content-center border-t-color border-b-color border-l-color px-3 cursor-text select-none font-medium outline-none', number, container);
5661
5768
  slotDom.tabIndex = '1';
5662
5769
  if (this.disabled) {
5663
5770
  slotDom.classList.add('disabled');
@@ -6061,6 +6168,9 @@
6061
6168
  slider.style.setProperty('--range-fix-max-offset', `${diffMaxOffset}rem`);
6062
6169
  }
6063
6170
  };
6171
+ this.onSetDisabled = (disabled) => {
6172
+ slider.disabled = disabled;
6173
+ };
6064
6174
  const container = document.createElement('div');
6065
6175
  container.className = 'lexrange relative py-3';
6066
6176
  this.root.appendChild(container);
@@ -6169,6 +6279,9 @@
6169
6279
  const realNameWidth = this.root.domName?.style.width ?? '0px';
6170
6280
  container.style.width = `calc( 100% - ${realNameWidth})`;
6171
6281
  };
6282
+ this.onSetDisabled = (disabled) => {
6283
+ container.dataset['disabled'] = disabled.toString();
6284
+ };
6172
6285
  const container = document.createElement('div');
6173
6286
  container.className = 'lexrate relative data-[disabled=true]:pointer-events-none';
6174
6287
  container.dataset['disabled'] = this.disabled.toString();
@@ -7312,6 +7425,9 @@
7312
7425
  const realNameWidth = this.root.domName?.style.width ?? '0px';
7313
7426
  tagsContainer.style.width = `calc( 100% - ${realNameWidth})`;
7314
7427
  };
7428
+ this.onSetDisabled = (disabled) => {
7429
+ this.generateTags(arrayValue);
7430
+ };
7315
7431
  // Show tags
7316
7432
  const tagsContainer = document.createElement('div');
7317
7433
  tagsContainer.className = 'inline-flex flex-wrap gap-1 bg-card/50 rounded-lg pad-xs [&_input]:w-2/3';
@@ -7380,6 +7496,11 @@
7380
7496
  const realNameWidth = this.root.domName?.style.width ?? '0px';
7381
7497
  container.style.width = options.inputWidth ?? `calc( 100% - ${realNameWidth})`;
7382
7498
  };
7499
+ this.onSetDisabled = (disabled) => {
7500
+ const textarea = this.root.querySelector('textarea');
7501
+ if (textarea)
7502
+ textarea.disabled = disabled;
7503
+ };
7383
7504
  let container = document.createElement('div');
7384
7505
  container.className = 'lextextarea';
7385
7506
  container.style.display = 'flex';
@@ -7497,6 +7618,9 @@
7497
7618
  const realNameWidth = this.root.domName?.style.width ?? '0px';
7498
7619
  container.style.width = options.inputWidth ?? `calc( 100% - ${realNameWidth})`;
7499
7620
  };
7621
+ this.onSetDisabled = (disabled) => {
7622
+ toggle.disabled = disabled;
7623
+ };
7500
7624
  var container = document.createElement('div');
7501
7625
  container.className = 'flex flex-row gap-2 items-center';
7502
7626
  this.root.appendChild(container);
@@ -7566,6 +7690,12 @@
7566
7690
  const realNameWidth = this.root.domName?.style.width ?? '0px';
7567
7691
  container.style.width = `calc( 100% - ${realNameWidth})`;
7568
7692
  };
7693
+ this.onSetDisabled = (disabled) => {
7694
+ const inputs = this.root.querySelectorAll('input');
7695
+ inputs.forEach((i) => {
7696
+ i.disabled = disabled;
7697
+ });
7698
+ };
7569
7699
  this.setLimits = (newMin, newMax, newStep) => { };
7570
7700
  const vectorInputs = [];
7571
7701
  var container = document.createElement('div');
@@ -8310,6 +8440,19 @@
8310
8440
  component.type = exports.ComponentType.LABEL;
8311
8441
  return component;
8312
8442
  }
8443
+ /**
8444
+ * @method addDescription
8445
+ * @param {String} value Information string
8446
+ * @param {Object} options Text options
8447
+ */
8448
+ addDescription(value, options = {}) {
8449
+ options.disabled = true;
8450
+ options.fitHeight = true;
8451
+ options.inputClass = exports.LX.mergeClass('bg-none', options.inputClass);
8452
+ const component = this.addTextArea(null, value, null, options);
8453
+ component.type = exports.ComponentType.DESCRIPTION;
8454
+ return component;
8455
+ }
8313
8456
  /**
8314
8457
  * @method addButton
8315
8458
  * @param {String} name Component name
@@ -8348,18 +8491,22 @@
8348
8491
  }
8349
8492
  /**
8350
8493
  * @method addCard
8351
- * @param {String} name Card Name
8352
- * @param {Object} options:
8353
- * text: Card text
8354
- * link: Card link
8355
- * title: Card dom title
8356
- * src: url of the image
8357
- * callback (Function): function to call on click
8494
+ * @param {String} name
8495
+ * @param {Object} options
8358
8496
  */
8359
8497
  addCard(name, options = {}) {
8360
8498
  const component = new Card(name, options);
8361
8499
  return this._attachComponent(component);
8362
8500
  }
8501
+ /**
8502
+ * @method addEmpty
8503
+ * @param {String} name
8504
+ * @param {Object} options
8505
+ */
8506
+ addEmpty(name, options = {}) {
8507
+ const component = new Empty(name, options);
8508
+ return this._attachComponent(component);
8509
+ }
8363
8510
  /**
8364
8511
  * @method addForm
8365
8512
  * @param {String} name Component name
@@ -11174,6 +11321,8 @@
11174
11321
  // Watch for trigger being removed from the DOM before mouseleave fires
11175
11322
  rafId = requestAnimationFrame(_watchConnection);
11176
11323
  exports.LX.doAsync(() => {
11324
+ if (!tooltipDom)
11325
+ return;
11177
11326
  const position = [0, 0];
11178
11327
  const offsetX = parseFloat(trigger.dataset['tooltipOffsetX'] ?? _offsetX);
11179
11328
  const offsetY = parseFloat(trigger.dataset['tooltipOffsetY'] ?? _offsetY);
@@ -13339,7 +13488,9 @@
13339
13488
  // using a fullscreen SVG with "rect" elements
13340
13489
  _generateMask(reference) {
13341
13490
  this.tourContainer.innerHTML = ''; // Clear previous content
13491
+ const scrollTop = document.scrollingElement?.scrollTop ?? 0;
13342
13492
  this.tourMask = exports.LX.makeContainer(['100%', '100%'], 'tour-mask absolute inset-0');
13493
+ this.tourMask.style.top = `${scrollTop}px`;
13343
13494
  this.tourContainer.appendChild(this.tourMask);
13344
13495
  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
13345
13496
  svg.style.width = '100%';
@@ -13402,7 +13553,7 @@
13402
13553
  // Reference Highlight
13403
13554
  const refContainer = exports.LX.makeContainer(['0', '0'], 'tour-ref-mask absolute');
13404
13555
  refContainer.style.left = `${boundingX - hOffset - 1}px`;
13405
- refContainer.style.top = `${boundingY - vOffset - 1}px`;
13556
+ refContainer.style.top = `${boundingY - vOffset - 1 + scrollTop}px`;
13406
13557
  refContainer.style.width = `${boundingWidth + hOffset * 2 + 2}px`;
13407
13558
  refContainer.style.height = `${boundingHeight + vOffset * 2 + 2}px`;
13408
13559
  this.tourContainer.appendChild(refContainer);
@@ -14447,12 +14598,12 @@
14447
14598
  getText(separator = '\n') {
14448
14599
  return this._lines.join(separator);
14449
14600
  }
14450
- setText(text) {
14601
+ setText(text, silent = false) {
14451
14602
  this._lines = text.split(/\r?\n/);
14452
14603
  if (this._lines.length === 0) {
14453
14604
  this._lines = [''];
14454
14605
  }
14455
- if (this.onChange)
14606
+ if (!silent && this.onChange)
14456
14607
  this.onChange(this);
14457
14608
  }
14458
14609
  getCharAt(line, col) {
@@ -14616,6 +14767,7 @@
14616
14767
  _lastPushTime = 0;
14617
14768
  _groupThresholdMs;
14618
14769
  _maxSteps;
14770
+ _savedDepth = 0;
14619
14771
  constructor(groupThresholdMs = 2000, maxSteps = 200) {
14620
14772
  this._groupThresholdMs = groupThresholdMs;
14621
14773
  this._maxSteps = maxSteps;
@@ -14687,11 +14839,19 @@
14687
14839
  canRedo() {
14688
14840
  return this._redoStack.length > 0;
14689
14841
  }
14842
+ markSaved() {
14843
+ this._flush();
14844
+ this._savedDepth = this._undoStack.length;
14845
+ }
14846
+ isModified() {
14847
+ return this._undoStack.length !== this._savedDepth || this._pendingOps.length > 0;
14848
+ }
14690
14849
  clear() {
14691
14850
  this._undoStack.length = 0;
14692
14851
  this._redoStack.length = 0;
14693
14852
  this._pendingOps.length = 0;
14694
14853
  this._lastPushTime = 0;
14854
+ this._savedDepth = 0;
14695
14855
  }
14696
14856
  _flush() {
14697
14857
  if (this._pendingOps.length === 0)
@@ -15260,8 +15420,36 @@
15260
15420
  exports.LX.Panel;
15261
15421
  exports.LX.Tabs;
15262
15422
  exports.LX.NodeTree;
15263
- /** Matches hex color literals: #rgb #rgba #rrggbb #rrggbbaa */
15264
15423
  const HEX_COLOR_RE = /#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{4}|[0-9a-fA-F]{3})\b/g;
15424
+ const URL_REGEX = /(https?:\/\/[^\s"'<>)\]]+)/g;
15425
+ /**
15426
+ * Returns true if the string token at `idx` in the token list is a module import path.
15427
+ */
15428
+ function isImportPath(tokens, idx) {
15429
+ const isWs = (t) => /^\s+$/.test(t.value);
15430
+ const isImportWord = (t) => t.value === 'require' || t.value === 'import';
15431
+ for (let i = idx - 1; i >= 0; i--) {
15432
+ const t = tokens[i];
15433
+ if (isWs(t))
15434
+ continue;
15435
+ if (t.type === 'keyword' && t.value === 'from')
15436
+ return true;
15437
+ if (isImportWord(t))
15438
+ return true;
15439
+ if (t.type === 'symbol' && t.value === '(') {
15440
+ for (let j = i - 1; j >= 0; j--) {
15441
+ const t2 = tokens[j];
15442
+ if (isWs(t2))
15443
+ continue;
15444
+ if (isImportWord(t2))
15445
+ return true;
15446
+ break;
15447
+ }
15448
+ }
15449
+ break;
15450
+ }
15451
+ return false;
15452
+ }
15265
15453
  /**
15266
15454
  * Scans a raw token value for hex color literals and returns HTML with each
15267
15455
  * color wrapped in a swatch span. Non-color text is HTML-escaped.
@@ -15304,6 +15492,15 @@
15304
15492
  this.thumb = exports.LX.makeElement('div');
15305
15493
  this.thumb.addEventListener('mousedown', (e) => this._onMouseDown(e));
15306
15494
  this.root.appendChild(this.thumb);
15495
+ this.root.addEventListener('mousedown', (e) => {
15496
+ if (e.target === this.thumb)
15497
+ return;
15498
+ const clickPos = this._vertical ? e.offsetY : e.offsetX;
15499
+ const thumbSize = this._vertical ? this.thumb.offsetHeight : this.thumb.offsetWidth;
15500
+ const delta = (clickPos - thumbSize / 2) - this._thumbPos;
15501
+ this._onDrag?.(delta);
15502
+ this._onMouseDown(e); // continue as drag from new position
15503
+ });
15307
15504
  }
15308
15505
  setThumbRatio(ratio) {
15309
15506
  this._thumbRatio = exports.LX.clamp(ratio, 0, 1);
@@ -15427,6 +15624,7 @@
15427
15624
  onReady;
15428
15625
  onCreateFile;
15429
15626
  onCodeChange;
15627
+ onOpenPath;
15430
15628
  onHoverSymbol;
15431
15629
  _inputArea;
15432
15630
  // State:
@@ -15507,6 +15705,7 @@
15507
15705
  this.onSelectTab = options.onSelectTab;
15508
15706
  this.onReady = options.onReady;
15509
15707
  this.onCodeChange = options.onCodeChange;
15708
+ this.onOpenPath = options.onOpenPath;
15510
15709
  this.onHoverSymbol = options.onHoverSymbol;
15511
15710
  this.language = Tokenizer.getLanguage(this.highlight) ?? Tokenizer.getLanguage('Plain Text');
15512
15711
  this.symbolTable = new SymbolTable();
@@ -15717,19 +15916,31 @@
15717
15916
  this.codeArea.root.addEventListener('mousedown', this._onMouseDown.bind(this));
15718
15917
  this.codeArea.root.addEventListener('contextmenu', this._onMouseDown.bind(this));
15719
15918
  this.codeArea.root.addEventListener('mouseover', (e) => {
15720
- const link = e.target.closest('.code-link');
15919
+ const target = e.target;
15920
+ const link = target.closest('.code-link');
15721
15921
  if (link && e.ctrlKey)
15722
15922
  link.classList.add('hovered');
15923
+ const path = target.closest('.code-path');
15924
+ if (path && e.ctrlKey)
15925
+ path.classList.add('hovered');
15723
15926
  });
15724
15927
  this.codeArea.root.addEventListener('mouseout', (e) => {
15725
- const link = e.target.closest('.code-link');
15928
+ const target = e.target;
15929
+ const link = target.closest('.code-link');
15726
15930
  if (link)
15727
15931
  link.classList.remove('hovered');
15932
+ const path = target.closest('.code-path');
15933
+ if (path)
15934
+ path.classList.remove('hovered');
15728
15935
  });
15729
15936
  this.codeArea.root.addEventListener('mousemove', (e) => {
15730
- const link = e.target.closest('.code-link');
15937
+ const target = e.target;
15938
+ const link = target.closest('.code-link');
15731
15939
  if (link)
15732
15940
  link.classList.toggle('hovered', e.ctrlKey);
15941
+ const path = target.closest('.code-path');
15942
+ if (path)
15943
+ path.classList.toggle('hovered', e.ctrlKey);
15733
15944
  this._onCodeAreaMouseMove(e);
15734
15945
  });
15735
15946
  this.codeArea.root.addEventListener('mouseleave', () => {
@@ -15828,7 +16039,7 @@
15828
16039
  setText(text, language, detectLang = false) {
15829
16040
  if (!this.currentTab)
15830
16041
  return;
15831
- this.doc.setText(this._normalizeText(text));
16042
+ this.doc.setText(this._normalizeText(text), true);
15832
16043
  this.cursorSet.set(0, 0);
15833
16044
  this.undoManager.clear();
15834
16045
  this._lineStates = [];
@@ -16019,10 +16230,14 @@
16019
16230
  const codeTab = {
16020
16231
  name,
16021
16232
  dom,
16022
- doc: new CodeDocument(this.onCodeChange),
16233
+ doc: new CodeDocument((doc) => {
16234
+ this._setTabModified(name, true);
16235
+ this.onCodeChange?.(doc);
16236
+ }),
16023
16237
  cursorSet: new CursorSet(),
16024
16238
  undoManager: new UndoManager(),
16025
16239
  language: langName,
16240
+ modified: false,
16026
16241
  title: options.title ?? name
16027
16242
  };
16028
16243
  this._openedTabs[name] = codeTab;
@@ -16046,7 +16261,7 @@
16046
16261
  // Move into the sizer..
16047
16262
  this.codeSizer.appendChild(dom);
16048
16263
  if (options.text) {
16049
- codeTab.doc.setText(options.text);
16264
+ codeTab.doc.setText(options.text, true);
16050
16265
  codeTab.cursorSet.set(0, 0);
16051
16266
  codeTab.undoManager.clear();
16052
16267
  this._renderAllLines();
@@ -16137,7 +16352,7 @@
16137
16352
  title: options.title ?? name,
16138
16353
  language: langName
16139
16354
  });
16140
- this.doc.setText(text);
16355
+ this.doc.setText(text, true);
16141
16356
  this.setLanguage(langName, ext);
16142
16357
  this.cursorSet.set(0, 0);
16143
16358
  this.undoManager.clear();
@@ -16198,7 +16413,7 @@
16198
16413
  language: langName
16199
16414
  });
16200
16415
  if (results.length === 0) {
16201
- this.doc.setText(processedText);
16416
+ this.doc.setText(processedText, true);
16202
16417
  this.setLanguage(langName, ext);
16203
16418
  this.cursorSet.set(0, 0);
16204
16419
  this.undoManager.clear();
@@ -16240,6 +16455,30 @@
16240
16455
  }
16241
16456
  }, 20);
16242
16457
  }
16458
+ _findTabByPath(importPath) {
16459
+ // By now only uses base name
16460
+ const importBase = importPath.split('/').pop().replace(/\.\w+$/, '').toLowerCase();
16461
+ const allNames = new Set([
16462
+ ...Object.keys(this._openedTabs),
16463
+ ...Object.keys(this._loadedTabs),
16464
+ ...Object.keys(this._storedTabs),
16465
+ ]);
16466
+ for (const name of allNames) {
16467
+ const tabBase = name.split('/').pop().replace(/\.\w+$/, '').toLowerCase();
16468
+ if (tabBase === importBase)
16469
+ return name;
16470
+ }
16471
+ return null;
16472
+ }
16473
+ _setTabModified(name, modified) {
16474
+ const tab = this._openedTabs[name];
16475
+ if (!tab || tab.modified === modified)
16476
+ return;
16477
+ tab.modified = modified;
16478
+ const tabEl = this.tabs?.tabDOMs?.[name];
16479
+ if (tabEl)
16480
+ tabEl.toggleAttribute('data-modified', modified);
16481
+ }
16243
16482
  _onSelectTab(isNewTabButton, event, name) {
16244
16483
  if (this.disableEdition) {
16245
16484
  return;
@@ -16465,7 +16704,6 @@
16465
16704
  const lineText = this.doc.getLine(lineIndex);
16466
16705
  const result = Tokenizer.tokenizeLine(lineText, this.language, prevState);
16467
16706
  const langClass = this.language.name.toLowerCase().replace(/[^a-z]/g, '');
16468
- const URL_REGEX = /(https?:\/\/[^\s"'<>)\]]+)/g;
16469
16707
  // Pre-compute which token index gets the bracket-highlight class
16470
16708
  let bracketTokenIdx = -1;
16471
16709
  if (lineIndex === this._bracketOpenLine) {
@@ -16493,15 +16731,20 @@
16493
16731
  const cls = TOKEN_CLASS_MAP[token.type];
16494
16732
  const tokenCol = colOffset;
16495
16733
  colOffset += token.value.length;
16734
+ // Inject content depending on type of token: color, url, path?
16496
16735
  let content;
16497
16736
  if (token.type === 'comment') {
16498
- // Escape then inject clickable URL spans
16499
16737
  const escaped = token.value
16500
16738
  .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
16501
16739
  content = escaped.replace(URL_REGEX, `<span class="code-link" data-url="$1">$1</span>`);
16502
16740
  }
16741
+ else if (token.type === 'string' && isImportPath(result.tokens, ti)) {
16742
+ const inner = token.value.slice(1, -1); // strip surrounding quotes
16743
+ const q = token.value[0];
16744
+ const escapedInner = inner.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
16745
+ content = `${q}<span class="code-path" data-path="${inner}">${escapedInner}</span>${q}`;
16746
+ }
16503
16747
  else {
16504
- // Escape and inject color swatches for hex color strings
16505
16748
  content = injectColorSpans(token.value, lineIndex, tokenCol);
16506
16749
  }
16507
16750
  const bracketClass = ti === bracketTokenIdx ? ' code-bracket-active' : '';
@@ -16771,6 +17014,8 @@
16771
17014
  e.preventDefault();
16772
17015
  if (this.onSave) {
16773
17016
  this.onSave(this.getText(), this);
17017
+ this.undoManager.markSaved();
17018
+ this._setTabModified(this.currentTab.name, false);
16774
17019
  }
16775
17020
  return;
16776
17021
  case 'z':
@@ -16793,6 +17038,17 @@
16793
17038
  e.preventDefault();
16794
17039
  this._doPaste();
16795
17040
  return;
17041
+ case 'home':
17042
+ e.preventDefault();
17043
+ this.cursorSet.set(0, 0);
17044
+ this._afterCursorMove();
17045
+ return;
17046
+ case 'end':
17047
+ e.preventDefault();
17048
+ const lastLine = this.doc.lineCount - 1;
17049
+ this.cursorSet.set(lastLine, this.doc.getLine(lastLine).length);
17050
+ this._afterCursorMove();
17051
+ return;
16796
17052
  case ' ':
16797
17053
  e.preventDefault();
16798
17054
  // Also call user callback if provided
@@ -17565,6 +17821,8 @@
17565
17821
  }
17566
17822
  this._rebuildLines();
17567
17823
  this._afterCursorMove();
17824
+ if (this.currentTab)
17825
+ this._setTabModified(this.currentTab.name, this.undoManager.isModified());
17568
17826
  }
17569
17827
  }
17570
17828
  _doRedo() {
@@ -17576,6 +17834,8 @@
17576
17834
  }
17577
17835
  this._rebuildLines();
17578
17836
  this._afterCursorMove();
17837
+ if (this.currentTab)
17838
+ this._setTabModified(this.currentTab.name, this.undoManager.isModified());
17579
17839
  }
17580
17840
  }
17581
17841
  // Mouse input events:
@@ -17586,7 +17846,7 @@
17586
17846
  return;
17587
17847
  if (this.autocomplete && this.autocomplete.contains(e.target))
17588
17848
  return;
17589
- // Ctrl+click: open link if cursor is over a code-link span
17849
+ // Ctrl+click: open link or import path
17590
17850
  if (e.ctrlKey && e.button === 0) {
17591
17851
  const target = e.target;
17592
17852
  const link = target.closest('.code-link');
@@ -17594,6 +17854,15 @@
17594
17854
  window.open(link.dataset.url, '_blank');
17595
17855
  return;
17596
17856
  }
17857
+ const pathEl = target.closest('.code-path');
17858
+ if (pathEl?.dataset.path) {
17859
+ const rawPath = pathEl.dataset.path;
17860
+ const tabName = this._findTabByPath(rawPath);
17861
+ if (tabName)
17862
+ this.loadTab(tabName);
17863
+ this.onOpenPath?.(rawPath, this);
17864
+ return;
17865
+ }
17597
17866
  }
17598
17867
  e.preventDefault(); // Prevent browser from stealing focus from _inputArea
17599
17868
  this._wasPaired = false;
@@ -17641,18 +17910,55 @@
17641
17910
  }
17642
17911
  this._afterCursorMove();
17643
17912
  this._inputArea.focus();
17644
- // Track mouse for drag selection
17645
- const onMouseMove = (me) => {
17646
- const mx = me.clientX - rect.left - this.xPadding;
17647
- const my = me.clientY - rect.top;
17648
- const ml = Math.max(0, Math.min(Math.floor(my / this.lineHeight), this.doc.lineCount - 1));
17649
- const mc = Math.max(0, Math.min(Math.round(mx / this.charWidth), this.doc.getLine(ml).length));
17913
+ // Track mouse for drag selection (with auto-scroll when outside editor window/area)
17914
+ let lastMouseX = 0;
17915
+ let lastMouseY = 0;
17916
+ let rafId = null;
17917
+ const updateSelection = () => {
17918
+ const currentRect = this.codeContainer.getBoundingClientRect();
17919
+ const mx = lastMouseX - currentRect.left - this.xPadding;
17920
+ const my = lastMouseY - currentRect.top;
17921
+ const ml = exports.LX.clamp(Math.floor(my / this.lineHeight), 0, this.doc.lineCount - 1);
17922
+ const mc = exports.LX.clamp(Math.round(mx / this.charWidth), 0, this.doc.getLine(ml).length);
17650
17923
  const sel = this.cursorSet.getPrimary();
17651
17924
  sel.head = { line: ml, col: mc };
17652
17925
  this._renderCursors();
17653
17926
  this._renderSelections();
17654
17927
  };
17928
+ const autoScroll = () => {
17929
+ const scrollerRect = this.codeScroller.getBoundingClientRect();
17930
+ const overshootY = lastMouseY < scrollerRect.top ? lastMouseY - scrollerRect.top
17931
+ : lastMouseY > scrollerRect.bottom ? lastMouseY - scrollerRect.bottom : 0;
17932
+ const overshootX = lastMouseX < scrollerRect.left ? lastMouseX - scrollerRect.left
17933
+ : lastMouseX > scrollerRect.right ? lastMouseX - scrollerRect.right : 0;
17934
+ if (overshootY === 0 && overshootX === 0) {
17935
+ rafId = null;
17936
+ return;
17937
+ }
17938
+ const speedY = Math.sign(overshootY) * Math.min(Math.abs(overshootY) * 0.3, 15);
17939
+ const speedX = Math.sign(overshootX) * Math.min(Math.abs(overshootX) * 0.3, 15);
17940
+ this.codeScroller.scrollTop += speedY;
17941
+ this.codeScroller.scrollLeft += speedX;
17942
+ this._syncScrollBars();
17943
+ updateSelection();
17944
+ rafId = requestAnimationFrame(autoScroll);
17945
+ };
17946
+ const onMouseMove = (me) => {
17947
+ lastMouseX = me.clientX;
17948
+ lastMouseY = me.clientY;
17949
+ updateSelection();
17950
+ const scrollerRect = this.codeScroller.getBoundingClientRect();
17951
+ const isOutside = me.clientY < scrollerRect.top || me.clientY > scrollerRect.bottom
17952
+ || me.clientX < scrollerRect.left || me.clientX > scrollerRect.right;
17953
+ if (isOutside && rafId === null) {
17954
+ rafId = requestAnimationFrame(autoScroll);
17955
+ }
17956
+ };
17655
17957
  const onMouseUp = () => {
17958
+ if (rafId !== null) {
17959
+ cancelAnimationFrame(rafId);
17960
+ rafId = null;
17961
+ }
17656
17962
  document.removeEventListener('mousemove', onMouseMove);
17657
17963
  document.removeEventListener('mouseup', onMouseUp);
17658
17964
  };
@@ -17713,7 +18019,10 @@
17713
18019
  const suggestions = [];
17714
18020
  const added = new Set();
17715
18021
  const addSuggestion = (s) => {
17716
- if (!added.has(s.label)) {
18022
+ if (added.has(s.label)) {
18023
+ suggestions[suggestions.findIndex(x => x.label === s.label)] = s;
18024
+ }
18025
+ else {
17717
18026
  suggestions.push(s);
17718
18027
  added.add(s.label);
17719
18028
  }
@@ -17726,9 +18035,12 @@
17726
18035
  return suggestion.label.toLowerCase().startsWith(w);
17727
18036
  };
17728
18037
  // Get first suggestions from symbol table
18038
+ const _skipKinds = new Set(['constructor-call', 'method-call']);
17729
18039
  const allSymbols = this.symbolTable.getAllSymbols();
17730
18040
  for (const symbol of allSymbols) {
17731
- const s = { label: symbol.name, kind: symbol.kind, scope: symbol.scope, detail: `${symbol.kind} in ${symbol.scope}` };
18041
+ if (_skipKinds.has(symbol.kind))
18042
+ continue;
18043
+ const s = { label: symbol.name, kind: symbol.kind, scope: symbol.scope };
17732
18044
  if (filterSuggestion(s, word))
17733
18045
  addSuggestion(s);
17734
18046
  }
@@ -17762,7 +18074,7 @@
17762
18074
  // Render suggestions
17763
18075
  suggestions.forEach((suggestion, index) => {
17764
18076
  const item = document.createElement('pre');
17765
- item.insertText = suggestion.insertText ?? suggestion.label;
18077
+ item.suggestionData = suggestion;
17766
18078
  if (index === this._selectedAutocompleteIndex)
17767
18079
  item.classList.add('selected');
17768
18080
  const currSuggestionLabel = suggestion.label;
@@ -17791,11 +18103,15 @@
17791
18103
  break;
17792
18104
  case 'type':
17793
18105
  iconName = 'Type';
17794
- iconClass = 'text-teal-500';
18106
+ iconClass = 'text-purple-500';
17795
18107
  break;
17796
18108
  case 'function':
17797
18109
  iconName = 'Function';
17798
- iconClass = 'text-purple-500';
18110
+ iconClass = 'text-teal-500';
18111
+ break;
18112
+ case 'constant':
18113
+ iconName = 'Pi';
18114
+ iconClass = 'text-rose-600';
17799
18115
  break;
17800
18116
  case 'method':
17801
18117
  iconName = 'Box';
@@ -17809,14 +18125,6 @@
17809
18125
  iconName = 'Layers';
17810
18126
  iconClass = 'text-blue-300';
17811
18127
  break;
17812
- case 'constructor-call':
17813
- iconName = 'Hammer';
17814
- iconClass = 'text-green-500';
17815
- break;
17816
- case 'method-call':
17817
- iconName = 'Parentheses';
17818
- iconClass = 'text-gray-400';
17819
- break;
17820
18128
  }
17821
18129
  item.appendChild(exports.LX.makeIcon(iconName, { iconClass: 'ml-1 mr-2', svgClass: 'sm ' + iconClass }));
17822
18130
  // Highlight the written part
@@ -17831,7 +18139,13 @@
17831
18139
  var postWord = document.createElement('span');
17832
18140
  postWord.textContent = currSuggestionLabel.substring(hIndex + word.length);
17833
18141
  item.appendChild(postWord);
17834
- if (suggestion.kind) {
18142
+ if (suggestion.detail) {
18143
+ const detail = document.createElement('span');
18144
+ detail.textContent = ` ${suggestion.detail}`;
18145
+ detail.className = 'kind text-muted-foreground text-xs! ml-2';
18146
+ item.appendChild(detail);
18147
+ }
18148
+ else if (suggestion.kind) {
17835
18149
  const kind = document.createElement('span');
17836
18150
  kind.textContent = ` (${suggestion.kind})`;
17837
18151
  kind.className = 'kind text-muted-foreground text-xs! ml-2';
@@ -17877,9 +18191,12 @@
17877
18191
  * Insert the selected autocomplete word at cursor.
17878
18192
  */
17879
18193
  _doAutocompleteWord() {
17880
- const text = this._getSelectedAutoCompleteWord();
17881
- if (!text)
18194
+ const suggestion = this._getSelectedAutoCompleteSuggestion();
18195
+ if (!suggestion)
17882
18196
  return;
18197
+ const text = suggestion.insertText ?? suggestion.label;
18198
+ const cursorOffset = suggestion.cursorOffset; // only valid in single line autocomplete
18199
+ const selectLength = suggestion.selectLength;
17883
18200
  const cursor = this.cursorSet.getPrimary().head;
17884
18201
  const { start, end } = this._getWordAtCursor();
17885
18202
  const line = cursor.line;
@@ -17891,7 +18208,13 @@
17891
18208
  const insertOp = this.doc.insert(line, start, text);
17892
18209
  const insertedLines = text.split(/\r?\n/);
17893
18210
  if (insertedLines.length === 1) {
17894
- this.cursorSet.set(line, start + text.length);
18211
+ const cursorCol = start + (cursorOffset ?? text.length);
18212
+ this.cursorSet.set(line, cursorCol);
18213
+ if (selectLength) {
18214
+ const sel = this.cursorSet.getPrimary();
18215
+ sel.anchor = { line, col: cursorCol };
18216
+ sel.head = { line, col: cursorCol + selectLength };
18217
+ }
17895
18218
  }
17896
18219
  else {
17897
18220
  this.cursorSet.set(line + insertedLines.length - 1, insertedLines[insertedLines.length - 1].length);
@@ -17902,11 +18225,11 @@
17902
18225
  this._afterCursorMove();
17903
18226
  this._doHideAutocomplete();
17904
18227
  }
17905
- _getSelectedAutoCompleteWord() {
18228
+ _getSelectedAutoCompleteSuggestion() {
17906
18229
  if (!this.autocomplete || !this._isAutoCompleteActive)
17907
18230
  return null;
17908
18231
  const pre = this.autocomplete.childNodes[this._selectedAutocompleteIndex];
17909
- return pre.insertText;
18232
+ return pre.suggestionData;
17910
18233
  }
17911
18234
  _afterCursorMove() {
17912
18235
  this._renderCursors();
@@ -18147,6 +18470,8 @@
18147
18470
  this._hoverWord = '';
18148
18471
  }
18149
18472
  _onCodeAreaMouseMove(e) {
18473
+ if (!this.currentTab)
18474
+ return;
18150
18475
  // Only show hover when no button is pressed (no dragging)
18151
18476
  if (e.buttons !== 0) {
18152
18477
  this._clearHoverPopup();
@@ -19325,7 +19650,7 @@
19325
19650
  }
19326
19651
  }
19327
19652
  else if (isFolder) {
19328
- that._enterFolder(item);
19653
+ that._requestEnterFolder(item);
19329
19654
  return;
19330
19655
  }
19331
19656
  const onSelect = that._callbacks['select'];
@@ -19543,17 +19868,17 @@
19543
19868
  if (!this.prevData.length || !this.currentFolder)
19544
19869
  return;
19545
19870
  this.nextData.push(this.currentFolder);
19546
- this._enterFolder(this.prevData.pop(), false);
19871
+ this._enterFolder(this.prevData.pop(), false, true);
19547
19872
  }, { buttonClass: 'ghost', title: 'Go Back', tooltip: true, icon: 'ArrowLeft' });
19548
19873
  panel.addButton(null, 'GoForwardButton', () => {
19549
19874
  if (!this.nextData.length || !this.currentFolder)
19550
19875
  return;
19551
- this._enterFolder(this.nextData.pop());
19876
+ this._enterFolder(this.nextData.pop(), false, true);
19552
19877
  }, { buttonClass: 'ghost', title: 'Go Forward', tooltip: true, icon: 'ArrowRight' });
19553
19878
  panel.addButton(null, 'GoUpButton', () => {
19554
19879
  const parentFolder = this.currentFolder?.parent;
19555
19880
  if (parentFolder)
19556
- this._enterFolder(parentFolder);
19881
+ this._enterFolder(parentFolder, false, true);
19557
19882
  }, { buttonClass: 'ghost', title: 'Go Upper Folder', tooltip: true, icon: 'ArrowUp' });
19558
19883
  panel.addButton(null, 'RefreshButton', () => {
19559
19884
  this._refreshContent(undefined, undefined, true);
@@ -19593,7 +19918,7 @@
19593
19918
  this._updatePath();
19594
19919
  }
19595
19920
  else {
19596
- this._enterFolder(node.type === 'folder' ? node : node.parent);
19921
+ this._requestEnterFolder(node.type === 'folder' ? node : node.parent);
19597
19922
  this._previewAsset(node);
19598
19923
  if (node.type !== 'folder') {
19599
19924
  this.content.querySelectorAll('.lexassetitem').forEach((i) => i.classList.remove('selected'));
@@ -19965,12 +20290,41 @@
19965
20290
  this.toolsPanel.refresh();
19966
20291
  this._refreshContent();
19967
20292
  }
19968
- async _enterFolder(folderItem, storeCurrent = true) {
20293
+ _requestEnterFolder(folderItem, storeCurrent = true) {
20294
+ if (!folderItem) {
20295
+ return;
20296
+ }
20297
+ const onBeforeEnterFolder = this._callbacks['beforeEnterFolder'];
20298
+ const onEnterFolder = this._callbacks['enterFolder'];
20299
+ const resolve = (...args) => {
20300
+ const child = this.currentData[0];
20301
+ const sameFolder = child?.parent?.metadata?.uid === folderItem.metadata?.uid;
20302
+ const mustRefresh = args[0] || !sameFolder;
20303
+ this._enterFolder(folderItem, storeCurrent, mustRefresh);
20304
+ const event = {
20305
+ type: 'enter-folder',
20306
+ to: folderItem,
20307
+ userInitiated: true
20308
+ };
20309
+ if (onEnterFolder)
20310
+ onEnterFolder(event, ...args);
20311
+ };
20312
+ if (onBeforeEnterFolder) {
20313
+ const event = {
20314
+ type: 'enter-folder',
20315
+ to: folderItem,
20316
+ userInitiated: true
20317
+ };
20318
+ onBeforeEnterFolder(event, resolve);
20319
+ }
20320
+ else {
20321
+ resolve();
20322
+ }
20323
+ }
20324
+ _enterFolder(folderItem, storeCurrent, mustRefresh) {
19969
20325
  if (!folderItem) {
19970
20326
  return;
19971
20327
  }
19972
- const child = this.currentData[0];
19973
- const sameFolder = child?.parent?.metadata?.uid === folderItem.metadata?.uid;
19974
20328
  if (storeCurrent) {
19975
20329
  this.prevData.push(this.currentFolder ?? {
19976
20330
  id: '/',
@@ -19979,17 +20333,6 @@
19979
20333
  metadata: {}
19980
20334
  });
19981
20335
  }
19982
- let mustRefresh = !sameFolder;
19983
- const onEnterFolder = this._callbacks['enterFolder'];
19984
- if (onEnterFolder !== undefined) {
19985
- const event = {
19986
- type: 'enter_folder',
19987
- to: folderItem,
19988
- userInitiated: true
19989
- };
19990
- const r = await onEnterFolder(event);
19991
- mustRefresh = mustRefresh || r;
19992
- }
19993
20336
  // Update this after the event since the user might have added or modified the data
19994
20337
  this.currentFolder = folderItem;
19995
20338
  this.currentData = this.currentFolder?.children ?? [];