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.
@@ -12,7 +12,7 @@ const g$2 = globalThis;
12
12
  let LX = g$2.LX;
13
13
  if (!LX) {
14
14
  LX = {
15
- version: '8.3.2',
15
+ version: '8.4.1',
16
16
  ready: false,
17
17
  extensions: [], // Store extensions used
18
18
  extraCommandbarEntries: [], // User specific entries for command bar
@@ -432,6 +432,8 @@ var ComponentType$1;
432
432
  ComponentType[ComponentType["LABEL"] = 39] = "LABEL";
433
433
  ComponentType[ComponentType["BLANK"] = 40] = "BLANK";
434
434
  ComponentType[ComponentType["RATE"] = 41] = "RATE";
435
+ ComponentType[ComponentType["EMPTY"] = 42] = "EMPTY";
436
+ ComponentType[ComponentType["DESCRIPTION"] = 43] = "DESCRIPTION";
435
437
  })(ComponentType$1 || (ComponentType$1 = {}));
436
438
  LX.ComponentType = ComponentType$1;
437
439
  /**
@@ -733,6 +735,9 @@ class Button extends BaseComponent$1 {
733
735
  const realNameWidth = this.root.domName?.style.width ?? '0px';
734
736
  wValue.style.width = `calc( 100% - ${realNameWidth})`;
735
737
  };
738
+ this.onSetDisabled = (disabled) => {
739
+ wValue.disabled = disabled;
740
+ };
736
741
  // In case of swap, set if a change has to be performed
737
742
  this.setState = function (v, skipCallback) {
738
743
  const swapInput = wValue.querySelector('input');
@@ -1470,6 +1475,12 @@ class NumberInput extends BaseComponent$1 {
1470
1475
  const realNameWidth = this.root.domName?.style.width ?? '0px';
1471
1476
  container.style.width = options.inputWidth ?? `calc( 100% - ${realNameWidth})`;
1472
1477
  };
1478
+ this.onSetDisabled = (disabled) => {
1479
+ vecinput.disabled = disabled;
1480
+ const slider = this.root.querySelector('input[type="range"]');
1481
+ if (slider)
1482
+ slider.disabled = disabled;
1483
+ };
1473
1484
  this.setLimits = (newMin, newMax, newStep) => { };
1474
1485
  var container = document.createElement('div');
1475
1486
  container.className = 'lexnumber';
@@ -1500,7 +1511,7 @@ class NumberInput extends BaseComponent$1 {
1500
1511
  if (!options.skipSlider && options.min !== undefined && options.max !== undefined) {
1501
1512
  let sliderBox = LX.makeContainer(['100%', 'auto'], 'z-1 input-box', '', box);
1502
1513
  let slider = document.createElement('input');
1503
- slider.className = 'lexinputslider';
1514
+ slider.className = 'lexinputslider disabled:pointer-events-none disabled:opacity-50';
1504
1515
  slider.min = options.min;
1505
1516
  slider.max = options.max;
1506
1517
  slider.step = options.step ?? 1;
@@ -1637,6 +1648,11 @@ class TextInput extends BaseComponent$1 {
1637
1648
  const realNameWidth = this.root.domName?.style.width ?? '0px';
1638
1649
  container.style.width = options.inputWidth ?? `calc( 100% - ${realNameWidth})`;
1639
1650
  };
1651
+ this.onSetDisabled = (disabled) => {
1652
+ const input = this.root.querySelector('input');
1653
+ if (input)
1654
+ input.disabled = disabled;
1655
+ };
1640
1656
  this.valid = (v, matchField) => {
1641
1657
  v = v ?? this.value();
1642
1658
  if (!options.pattern)
@@ -1796,6 +1812,9 @@ class Select extends BaseComponent$1 {
1796
1812
  const realNameWidth = this.root.domName?.style.width ?? '0px';
1797
1813
  container.style.width = options.inputWidth ?? `calc( 100% - ${realNameWidth})`;
1798
1814
  };
1815
+ this.onSetDisabled = (disabled) => {
1816
+ selectedOption?.setDisabled(disabled);
1817
+ };
1799
1818
  let container = document.createElement('div');
1800
1819
  container.className = 'lexselect';
1801
1820
  this.root.appendChild(container);
@@ -2070,6 +2089,13 @@ class ArrayInput extends BaseComponent$1 {
2070
2089
  this._trigger(new IEvent$1(name, values, event), callback);
2071
2090
  }
2072
2091
  };
2092
+ this.onSetDisabled = (disabled) => {
2093
+ if (this.root.dataset['opened'] == 'true' && disabled) {
2094
+ this.root.dataset['opened'] = false;
2095
+ this.root.querySelector('.lexarrayitems').toggleAttribute('hidden', true);
2096
+ }
2097
+ toggleButton.setDisabled(disabled);
2098
+ };
2073
2099
  // Add open array button
2074
2100
  let container = document.createElement('div');
2075
2101
  container.className = 'lexarray shrink-1 grow-1 ml-4';
@@ -2161,9 +2187,10 @@ class Card extends BaseComponent$1 {
2161
2187
  const container = LX.makeContainer(['100%', 'auto'], 'lexcard max-w-sm flex flex-col gap-4 bg-card border-color rounded-xl py-6', '', this.root);
2162
2188
  if (options.header) {
2163
2189
  const hasAction = options.header.action !== undefined;
2190
+ const actionButtonOptions = options.header.action.options ?? {};
2164
2191
  let header = LX.makeContainer(['100%', 'auto'], `flex ${hasAction ? 'flex-row gap-4' : 'flex-col gap-1'} px-6`, '', container);
2165
2192
  if (hasAction) {
2166
- const actionBtn = new Button(null, options.header.action.name, options.header.action.callback, { buttonClass: 'secondary' });
2193
+ const actionBtn = new Button(null, options.header.action.name, options.header.action.callback, { buttonClass: 'secondary', ...actionButtonOptions });
2167
2194
  header.appendChild(actionBtn.root);
2168
2195
  const titleDescBox = LX.makeContainer(['75%', 'auto'], `flex flex-col gap-1`, '');
2169
2196
  header.prepend(titleDescBox);
@@ -2230,6 +2257,9 @@ class Checkbox extends BaseComponent$1 {
2230
2257
  const realNameWidth = this.root.domName?.style.width ?? '0px';
2231
2258
  container.style.width = options.inputWidth ?? `calc( 100% - ${realNameWidth})`;
2232
2259
  };
2260
+ this.onSetDisabled = (disabled) => {
2261
+ checkbox.disabled = disabled;
2262
+ };
2233
2263
  let container = document.createElement('div');
2234
2264
  container.className = 'flex items-center gap-2 my-0 mx-auto [&_span]:truncate [&_span]:flex-auto-fill';
2235
2265
  this.root.appendChild(container);
@@ -2858,7 +2888,12 @@ class ColorInput extends BaseComponent$1 {
2858
2888
  const realNameWidth = this.root.domName?.style.width ?? '0px';
2859
2889
  container.style.width = `calc( 100% - ${realNameWidth})`;
2860
2890
  };
2861
- var container = document.createElement('span');
2891
+ this.onSetDisabled = (disabled) => {
2892
+ textComponent.setDisabled(disabled);
2893
+ sampleContainer.classList.toggle('pointer-events-none', disabled);
2894
+ sampleContainer.classList.toggle('opacity-50', disabled);
2895
+ };
2896
+ let container = document.createElement('span');
2862
2897
  container.className = 'lexcolor';
2863
2898
  this.root.appendChild(container);
2864
2899
  this.picker = new ColorPicker(value, {
@@ -3022,6 +3057,11 @@ class Counter extends BaseComponent$1 {
3022
3057
  this._trigger(new IEvent$1(name, newValue, event), callback);
3023
3058
  }
3024
3059
  };
3060
+ this.onSetDisabled = (disabled) => {
3061
+ substrButton.setDisabled(disabled);
3062
+ addButton.setDisabled(disabled);
3063
+ input.disabled = disabled;
3064
+ };
3025
3065
  this.count = value;
3026
3066
  const min = options.min ?? 0;
3027
3067
  const max = options.max ?? 100;
@@ -3767,6 +3807,12 @@ class DatePicker extends BaseComponent$1 {
3767
3807
  const realNameWidth = this.root.domName?.style.width ?? '0px';
3768
3808
  container.style.width = `calc( 100% - ${realNameWidth})`;
3769
3809
  };
3810
+ this.onSetDisabled = (disabled) => {
3811
+ const buttons = this.root.querySelectorAll('button');
3812
+ buttons.forEach((b) => {
3813
+ b.disabled = disabled;
3814
+ });
3815
+ };
3770
3816
  const container = LX.makeContainer(['auto', 'auto'], 'lexdate flex flex-row');
3771
3817
  this.root.appendChild(container);
3772
3818
  if (!dateAsRange) {
@@ -4159,6 +4205,52 @@ class Dial extends BaseComponent$1 {
4159
4205
  LX.CanvasDial = CanvasDial;
4160
4206
  LX.Dial = Dial;
4161
4207
 
4208
+ // Empty.ts @jxarco
4209
+ /**
4210
+ * @class Empty
4211
+ * @description Empty Component
4212
+ */
4213
+ class Empty extends BaseComponent$1 {
4214
+ constructor(name, options = {}) {
4215
+ options.hideName = true;
4216
+ super(ComponentType$1.EMPTY, name, null, options);
4217
+ this.root.classList.add('place-content-center');
4218
+ const container = LX.makeContainer(['100%', 'auto'], 'lexcard max-w-sm flex flex-col gap-4 bg-card border-color rounded-xl py-6', '', this.root);
4219
+ if (options.header) {
4220
+ let header = LX.makeContainer(['100%', 'auto'], `flex flex-col gap-4 px-6 items-center`, '', container);
4221
+ if (options.header.icon) {
4222
+ const icon = LX.makeIcon(options.header.icon, { iconClass: 'bg-secondary p-2 rounded-lg!', svgClass: 'lg' });
4223
+ header.appendChild(icon);
4224
+ }
4225
+ else if (options.header.avatar) {
4226
+ const avatar = new LX.Avatar(options.header.avatar);
4227
+ header.appendChild(avatar.root);
4228
+ }
4229
+ if (options.header.title) {
4230
+ LX.makeElement('div', 'text-center text-foreground leading-none font-medium', options.header.title, header);
4231
+ }
4232
+ if (options.header.description) {
4233
+ LX.makeElement('div', 'text-sm text-center text-balance text-muted-foreground', options.header.description, header);
4234
+ }
4235
+ }
4236
+ if (options.actions) {
4237
+ const content = LX.makeContainer(['100%', 'auto'], 'flex flex-row gap-1 px-6 justify-center', '', container);
4238
+ for (let a of options.actions) {
4239
+ const action = new LX.Button(null, a.name, a.callback, { buttonClass: "sm outline", ...a.options });
4240
+ content.appendChild(action.root);
4241
+ }
4242
+ }
4243
+ if (options.footer) {
4244
+ const footer = LX.makeContainer(['100%', 'auto'], 'flex flex-col gap-1 px-6', '', container);
4245
+ const elements = [].concat(options.footer);
4246
+ for (let e of elements) {
4247
+ footer.appendChild(e.root ? e.root : e);
4248
+ }
4249
+ }
4250
+ }
4251
+ }
4252
+ LX.Empty = Empty;
4253
+
4162
4254
  // FileInput.ts @jxarco
4163
4255
  /**
4164
4256
  * @class FileInput
@@ -4174,6 +4266,9 @@ class FileInput extends BaseComponent$1 {
4174
4266
  const realNameWidth = this.root.domName?.style.width ?? '0px';
4175
4267
  input.style.width = `calc( 100% - ${realNameWidth})`;
4176
4268
  };
4269
+ this.onSetDisabled = (disabled) => {
4270
+ input.disabled = disabled;
4271
+ };
4177
4272
  // Create hidden input
4178
4273
  let input = document.createElement('input');
4179
4274
  input.className = 'lexfileinput';
@@ -4381,6 +4476,9 @@ class Layers extends BaseComponent$1 {
4381
4476
  const realNameWidth = this.root.domName?.style.width ?? '0px';
4382
4477
  container.style.width = `calc( 100% - ${realNameWidth})`;
4383
4478
  };
4479
+ this.onSetDisabled = (disabled) => {
4480
+ this.setLayers(value);
4481
+ };
4384
4482
  const container = LX.makeElement('div', 'lexlayers grid', '', this.root);
4385
4483
  const maxBits = options.maxBits ?? 16;
4386
4484
  this.setLayers = (val) => {
@@ -4457,6 +4555,9 @@ class List extends BaseComponent$1 {
4457
4555
  const realNameWidth = this.root.domName?.style.width ?? '0px';
4458
4556
  listContainer.style.width = `calc( 100% - ${realNameWidth})`;
4459
4557
  };
4558
+ this.onSetDisabled = (disabled) => {
4559
+ this._updateValues(values);
4560
+ };
4460
4561
  this._updateValues = (newValues) => {
4461
4562
  values = newValues;
4462
4563
  listContainer.innerHTML = '';
@@ -4924,16 +5025,19 @@ class Map2D extends BaseComponent$1 {
4924
5025
  const realNameWidth = this.root.domName?.style.width ?? '0px';
4925
5026
  container.style.width = `calc( 100% - ${realNameWidth})`;
4926
5027
  };
5028
+ this.onSetDisabled = (disabled) => {
5029
+ openerButton.setDisabled(disabled);
5030
+ };
4927
5031
  var container = document.createElement('div');
4928
5032
  container.className = 'lexmap2d';
4929
5033
  this.root.appendChild(container);
4930
5034
  this.map2d = new CanvasMap2D(points, callback, options);
4931
- const calendarIcon = LX.makeIcon(options.mapIcon ?? 'SquareMousePointer');
4932
- const calendarButton = new Button(null, 'Open Map', () => {
4933
- this._popover = new Popover(calendarButton.root, [this.map2d]);
5035
+ const icon = LX.makeIcon(options.mapIcon ?? 'SquareMousePointer');
5036
+ const openerButton = new Button(null, 'Open Map', () => {
5037
+ this._popover = new Popover(openerButton.root, [this.map2d]);
4934
5038
  }, { buttonClass: `outline justify-between`, disabled: this.disabled });
4935
- calendarButton.root.querySelector('button').appendChild(calendarIcon);
4936
- container.appendChild(calendarButton.root);
5039
+ openerButton.root.querySelector('button').appendChild(icon);
5040
+ container.appendChild(openerButton.root);
4937
5041
  LX.doAsync(this.onResize.bind(this));
4938
5042
  }
4939
5043
  }
@@ -5640,6 +5744,9 @@ class OTPInput extends BaseComponent$1 {
5640
5744
  const realNameWidth = this.root.domName?.style.width ?? '0px';
5641
5745
  container.style.width = `calc( 100% - ${realNameWidth})`;
5642
5746
  };
5747
+ this.onSetDisabled = (disabled) => {
5748
+ _refreshInput(value);
5749
+ };
5643
5750
  const container = document.createElement('div');
5644
5751
  container.className = 'lexotp flex flex-row items-center';
5645
5752
  this.root.appendChild(container);
@@ -5653,7 +5760,7 @@ class OTPInput extends BaseComponent$1 {
5653
5760
  for (let j = 0; j < g.length; ++j) {
5654
5761
  let number = valueString[itemsCount++];
5655
5762
  number = number == 'x' ? '' : number;
5656
- const slotDom = 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);
5763
+ const slotDom = 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);
5657
5764
  slotDom.tabIndex = '1';
5658
5765
  if (this.disabled) {
5659
5766
  slotDom.classList.add('disabled');
@@ -6057,6 +6164,9 @@ class RangeInput extends BaseComponent$1 {
6057
6164
  slider.style.setProperty('--range-fix-max-offset', `${diffMaxOffset}rem`);
6058
6165
  }
6059
6166
  };
6167
+ this.onSetDisabled = (disabled) => {
6168
+ slider.disabled = disabled;
6169
+ };
6060
6170
  const container = document.createElement('div');
6061
6171
  container.className = 'lexrange relative py-3';
6062
6172
  this.root.appendChild(container);
@@ -6165,6 +6275,9 @@ class Rate extends BaseComponent$1 {
6165
6275
  const realNameWidth = this.root.domName?.style.width ?? '0px';
6166
6276
  container.style.width = `calc( 100% - ${realNameWidth})`;
6167
6277
  };
6278
+ this.onSetDisabled = (disabled) => {
6279
+ container.dataset['disabled'] = disabled.toString();
6280
+ };
6168
6281
  const container = document.createElement('div');
6169
6282
  container.className = 'lexrate relative data-[disabled=true]:pointer-events-none';
6170
6283
  container.dataset['disabled'] = this.disabled.toString();
@@ -7308,6 +7421,9 @@ class Tags extends BaseComponent$1 {
7308
7421
  const realNameWidth = this.root.domName?.style.width ?? '0px';
7309
7422
  tagsContainer.style.width = `calc( 100% - ${realNameWidth})`;
7310
7423
  };
7424
+ this.onSetDisabled = (disabled) => {
7425
+ this.generateTags(arrayValue);
7426
+ };
7311
7427
  // Show tags
7312
7428
  const tagsContainer = document.createElement('div');
7313
7429
  tagsContainer.className = 'inline-flex flex-wrap gap-1 bg-card/50 rounded-lg pad-xs [&_input]:w-2/3';
@@ -7376,6 +7492,11 @@ class TextArea extends BaseComponent$1 {
7376
7492
  const realNameWidth = this.root.domName?.style.width ?? '0px';
7377
7493
  container.style.width = options.inputWidth ?? `calc( 100% - ${realNameWidth})`;
7378
7494
  };
7495
+ this.onSetDisabled = (disabled) => {
7496
+ const textarea = this.root.querySelector('textarea');
7497
+ if (textarea)
7498
+ textarea.disabled = disabled;
7499
+ };
7379
7500
  let container = document.createElement('div');
7380
7501
  container.className = 'lextextarea';
7381
7502
  container.style.display = 'flex';
@@ -7493,6 +7614,9 @@ class Toggle extends BaseComponent$1 {
7493
7614
  const realNameWidth = this.root.domName?.style.width ?? '0px';
7494
7615
  container.style.width = options.inputWidth ?? `calc( 100% - ${realNameWidth})`;
7495
7616
  };
7617
+ this.onSetDisabled = (disabled) => {
7618
+ toggle.disabled = disabled;
7619
+ };
7496
7620
  var container = document.createElement('div');
7497
7621
  container.className = 'flex flex-row gap-2 items-center';
7498
7622
  this.root.appendChild(container);
@@ -7562,6 +7686,12 @@ class Vector extends BaseComponent$1 {
7562
7686
  const realNameWidth = this.root.domName?.style.width ?? '0px';
7563
7687
  container.style.width = `calc( 100% - ${realNameWidth})`;
7564
7688
  };
7689
+ this.onSetDisabled = (disabled) => {
7690
+ const inputs = this.root.querySelectorAll('input');
7691
+ inputs.forEach((i) => {
7692
+ i.disabled = disabled;
7693
+ });
7694
+ };
7565
7695
  this.setLimits = (newMin, newMax, newStep) => { };
7566
7696
  const vectorInputs = [];
7567
7697
  var container = document.createElement('div');
@@ -8306,6 +8436,19 @@ let Panel$2 = class Panel {
8306
8436
  component.type = ComponentType$1.LABEL;
8307
8437
  return component;
8308
8438
  }
8439
+ /**
8440
+ * @method addDescription
8441
+ * @param {String} value Information string
8442
+ * @param {Object} options Text options
8443
+ */
8444
+ addDescription(value, options = {}) {
8445
+ options.disabled = true;
8446
+ options.fitHeight = true;
8447
+ options.inputClass = LX.mergeClass('bg-none', options.inputClass);
8448
+ const component = this.addTextArea(null, value, null, options);
8449
+ component.type = ComponentType$1.DESCRIPTION;
8450
+ return component;
8451
+ }
8309
8452
  /**
8310
8453
  * @method addButton
8311
8454
  * @param {String} name Component name
@@ -8344,18 +8487,22 @@ let Panel$2 = class Panel {
8344
8487
  }
8345
8488
  /**
8346
8489
  * @method addCard
8347
- * @param {String} name Card Name
8348
- * @param {Object} options:
8349
- * text: Card text
8350
- * link: Card link
8351
- * title: Card dom title
8352
- * src: url of the image
8353
- * callback (Function): function to call on click
8490
+ * @param {String} name
8491
+ * @param {Object} options
8354
8492
  */
8355
8493
  addCard(name, options = {}) {
8356
8494
  const component = new Card(name, options);
8357
8495
  return this._attachComponent(component);
8358
8496
  }
8497
+ /**
8498
+ * @method addEmpty
8499
+ * @param {String} name
8500
+ * @param {Object} options
8501
+ */
8502
+ addEmpty(name, options = {}) {
8503
+ const component = new Empty(name, options);
8504
+ return this._attachComponent(component);
8505
+ }
8359
8506
  /**
8360
8507
  * @method addForm
8361
8508
  * @param {String} name Component name
@@ -11170,6 +11317,8 @@ function asTooltip(trigger, content, options = {}) {
11170
11317
  // Watch for trigger being removed from the DOM before mouseleave fires
11171
11318
  rafId = requestAnimationFrame(_watchConnection);
11172
11319
  LX.doAsync(() => {
11320
+ if (!tooltipDom)
11321
+ return;
11173
11322
  const position = [0, 0];
11174
11323
  const offsetX = parseFloat(trigger.dataset['tooltipOffsetX'] ?? _offsetX);
11175
11324
  const offsetY = parseFloat(trigger.dataset['tooltipOffsetY'] ?? _offsetY);
@@ -13335,7 +13484,9 @@ class Tour {
13335
13484
  // using a fullscreen SVG with "rect" elements
13336
13485
  _generateMask(reference) {
13337
13486
  this.tourContainer.innerHTML = ''; // Clear previous content
13487
+ const scrollTop = document.scrollingElement?.scrollTop ?? 0;
13338
13488
  this.tourMask = LX.makeContainer(['100%', '100%'], 'tour-mask absolute inset-0');
13489
+ this.tourMask.style.top = `${scrollTop}px`;
13339
13490
  this.tourContainer.appendChild(this.tourMask);
13340
13491
  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
13341
13492
  svg.style.width = '100%';
@@ -13398,7 +13549,7 @@ class Tour {
13398
13549
  // Reference Highlight
13399
13550
  const refContainer = LX.makeContainer(['0', '0'], 'tour-ref-mask absolute');
13400
13551
  refContainer.style.left = `${boundingX - hOffset - 1}px`;
13401
- refContainer.style.top = `${boundingY - vOffset - 1}px`;
13552
+ refContainer.style.top = `${boundingY - vOffset - 1 + scrollTop}px`;
13402
13553
  refContainer.style.width = `${boundingWidth + hOffset * 2 + 2}px`;
13403
13554
  refContainer.style.height = `${boundingHeight + vOffset * 2 + 2}px`;
13404
13555
  this.tourContainer.appendChild(refContainer);
@@ -14443,12 +14594,12 @@ class CodeDocument {
14443
14594
  getText(separator = '\n') {
14444
14595
  return this._lines.join(separator);
14445
14596
  }
14446
- setText(text) {
14597
+ setText(text, silent = false) {
14447
14598
  this._lines = text.split(/\r?\n/);
14448
14599
  if (this._lines.length === 0) {
14449
14600
  this._lines = [''];
14450
14601
  }
14451
- if (this.onChange)
14602
+ if (!silent && this.onChange)
14452
14603
  this.onChange(this);
14453
14604
  }
14454
14605
  getCharAt(line, col) {
@@ -14612,6 +14763,7 @@ class UndoManager {
14612
14763
  _lastPushTime = 0;
14613
14764
  _groupThresholdMs;
14614
14765
  _maxSteps;
14766
+ _savedDepth = 0;
14615
14767
  constructor(groupThresholdMs = 2000, maxSteps = 200) {
14616
14768
  this._groupThresholdMs = groupThresholdMs;
14617
14769
  this._maxSteps = maxSteps;
@@ -14683,11 +14835,19 @@ class UndoManager {
14683
14835
  canRedo() {
14684
14836
  return this._redoStack.length > 0;
14685
14837
  }
14838
+ markSaved() {
14839
+ this._flush();
14840
+ this._savedDepth = this._undoStack.length;
14841
+ }
14842
+ isModified() {
14843
+ return this._undoStack.length !== this._savedDepth || this._pendingOps.length > 0;
14844
+ }
14686
14845
  clear() {
14687
14846
  this._undoStack.length = 0;
14688
14847
  this._redoStack.length = 0;
14689
14848
  this._pendingOps.length = 0;
14690
14849
  this._lastPushTime = 0;
14850
+ this._savedDepth = 0;
14691
14851
  }
14692
14852
  _flush() {
14693
14853
  if (this._pendingOps.length === 0)
@@ -15256,8 +15416,36 @@ LX.Area;
15256
15416
  LX.Panel;
15257
15417
  LX.Tabs;
15258
15418
  LX.NodeTree;
15259
- /** Matches hex color literals: #rgb #rgba #rrggbb #rrggbbaa */
15260
15419
  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;
15420
+ const URL_REGEX = /(https?:\/\/[^\s"'<>)\]]+)/g;
15421
+ /**
15422
+ * Returns true if the string token at `idx` in the token list is a module import path.
15423
+ */
15424
+ function isImportPath(tokens, idx) {
15425
+ const isWs = (t) => /^\s+$/.test(t.value);
15426
+ const isImportWord = (t) => t.value === 'require' || t.value === 'import';
15427
+ for (let i = idx - 1; i >= 0; i--) {
15428
+ const t = tokens[i];
15429
+ if (isWs(t))
15430
+ continue;
15431
+ if (t.type === 'keyword' && t.value === 'from')
15432
+ return true;
15433
+ if (isImportWord(t))
15434
+ return true;
15435
+ if (t.type === 'symbol' && t.value === '(') {
15436
+ for (let j = i - 1; j >= 0; j--) {
15437
+ const t2 = tokens[j];
15438
+ if (isWs(t2))
15439
+ continue;
15440
+ if (isImportWord(t2))
15441
+ return true;
15442
+ break;
15443
+ }
15444
+ }
15445
+ break;
15446
+ }
15447
+ return false;
15448
+ }
15261
15449
  /**
15262
15450
  * Scans a raw token value for hex color literals and returns HTML with each
15263
15451
  * color wrapped in a swatch span. Non-color text is HTML-escaped.
@@ -15300,6 +15488,15 @@ class ScrollBar {
15300
15488
  this.thumb = LX.makeElement('div');
15301
15489
  this.thumb.addEventListener('mousedown', (e) => this._onMouseDown(e));
15302
15490
  this.root.appendChild(this.thumb);
15491
+ this.root.addEventListener('mousedown', (e) => {
15492
+ if (e.target === this.thumb)
15493
+ return;
15494
+ const clickPos = this._vertical ? e.offsetY : e.offsetX;
15495
+ const thumbSize = this._vertical ? this.thumb.offsetHeight : this.thumb.offsetWidth;
15496
+ const delta = (clickPos - thumbSize / 2) - this._thumbPos;
15497
+ this._onDrag?.(delta);
15498
+ this._onMouseDown(e); // continue as drag from new position
15499
+ });
15303
15500
  }
15304
15501
  setThumbRatio(ratio) {
15305
15502
  this._thumbRatio = LX.clamp(ratio, 0, 1);
@@ -15423,6 +15620,7 @@ class CodeEditor {
15423
15620
  onReady;
15424
15621
  onCreateFile;
15425
15622
  onCodeChange;
15623
+ onOpenPath;
15426
15624
  onHoverSymbol;
15427
15625
  _inputArea;
15428
15626
  // State:
@@ -15503,6 +15701,7 @@ class CodeEditor {
15503
15701
  this.onSelectTab = options.onSelectTab;
15504
15702
  this.onReady = options.onReady;
15505
15703
  this.onCodeChange = options.onCodeChange;
15704
+ this.onOpenPath = options.onOpenPath;
15506
15705
  this.onHoverSymbol = options.onHoverSymbol;
15507
15706
  this.language = Tokenizer.getLanguage(this.highlight) ?? Tokenizer.getLanguage('Plain Text');
15508
15707
  this.symbolTable = new SymbolTable();
@@ -15713,19 +15912,31 @@ class CodeEditor {
15713
15912
  this.codeArea.root.addEventListener('mousedown', this._onMouseDown.bind(this));
15714
15913
  this.codeArea.root.addEventListener('contextmenu', this._onMouseDown.bind(this));
15715
15914
  this.codeArea.root.addEventListener('mouseover', (e) => {
15716
- const link = e.target.closest('.code-link');
15915
+ const target = e.target;
15916
+ const link = target.closest('.code-link');
15717
15917
  if (link && e.ctrlKey)
15718
15918
  link.classList.add('hovered');
15919
+ const path = target.closest('.code-path');
15920
+ if (path && e.ctrlKey)
15921
+ path.classList.add('hovered');
15719
15922
  });
15720
15923
  this.codeArea.root.addEventListener('mouseout', (e) => {
15721
- const link = e.target.closest('.code-link');
15924
+ const target = e.target;
15925
+ const link = target.closest('.code-link');
15722
15926
  if (link)
15723
15927
  link.classList.remove('hovered');
15928
+ const path = target.closest('.code-path');
15929
+ if (path)
15930
+ path.classList.remove('hovered');
15724
15931
  });
15725
15932
  this.codeArea.root.addEventListener('mousemove', (e) => {
15726
- const link = e.target.closest('.code-link');
15933
+ const target = e.target;
15934
+ const link = target.closest('.code-link');
15727
15935
  if (link)
15728
15936
  link.classList.toggle('hovered', e.ctrlKey);
15937
+ const path = target.closest('.code-path');
15938
+ if (path)
15939
+ path.classList.toggle('hovered', e.ctrlKey);
15729
15940
  this._onCodeAreaMouseMove(e);
15730
15941
  });
15731
15942
  this.codeArea.root.addEventListener('mouseleave', () => {
@@ -15824,7 +16035,7 @@ class CodeEditor {
15824
16035
  setText(text, language, detectLang = false) {
15825
16036
  if (!this.currentTab)
15826
16037
  return;
15827
- this.doc.setText(this._normalizeText(text));
16038
+ this.doc.setText(this._normalizeText(text), true);
15828
16039
  this.cursorSet.set(0, 0);
15829
16040
  this.undoManager.clear();
15830
16041
  this._lineStates = [];
@@ -16015,10 +16226,14 @@ class CodeEditor {
16015
16226
  const codeTab = {
16016
16227
  name,
16017
16228
  dom,
16018
- doc: new CodeDocument(this.onCodeChange),
16229
+ doc: new CodeDocument((doc) => {
16230
+ this._setTabModified(name, true);
16231
+ this.onCodeChange?.(doc);
16232
+ }),
16019
16233
  cursorSet: new CursorSet(),
16020
16234
  undoManager: new UndoManager(),
16021
16235
  language: langName,
16236
+ modified: false,
16022
16237
  title: options.title ?? name
16023
16238
  };
16024
16239
  this._openedTabs[name] = codeTab;
@@ -16042,7 +16257,7 @@ class CodeEditor {
16042
16257
  // Move into the sizer..
16043
16258
  this.codeSizer.appendChild(dom);
16044
16259
  if (options.text) {
16045
- codeTab.doc.setText(options.text);
16260
+ codeTab.doc.setText(options.text, true);
16046
16261
  codeTab.cursorSet.set(0, 0);
16047
16262
  codeTab.undoManager.clear();
16048
16263
  this._renderAllLines();
@@ -16133,7 +16348,7 @@ class CodeEditor {
16133
16348
  title: options.title ?? name,
16134
16349
  language: langName
16135
16350
  });
16136
- this.doc.setText(text);
16351
+ this.doc.setText(text, true);
16137
16352
  this.setLanguage(langName, ext);
16138
16353
  this.cursorSet.set(0, 0);
16139
16354
  this.undoManager.clear();
@@ -16194,7 +16409,7 @@ class CodeEditor {
16194
16409
  language: langName
16195
16410
  });
16196
16411
  if (results.length === 0) {
16197
- this.doc.setText(processedText);
16412
+ this.doc.setText(processedText, true);
16198
16413
  this.setLanguage(langName, ext);
16199
16414
  this.cursorSet.set(0, 0);
16200
16415
  this.undoManager.clear();
@@ -16236,6 +16451,30 @@ class CodeEditor {
16236
16451
  }
16237
16452
  }, 20);
16238
16453
  }
16454
+ _findTabByPath(importPath) {
16455
+ // By now only uses base name
16456
+ const importBase = importPath.split('/').pop().replace(/\.\w+$/, '').toLowerCase();
16457
+ const allNames = new Set([
16458
+ ...Object.keys(this._openedTabs),
16459
+ ...Object.keys(this._loadedTabs),
16460
+ ...Object.keys(this._storedTabs),
16461
+ ]);
16462
+ for (const name of allNames) {
16463
+ const tabBase = name.split('/').pop().replace(/\.\w+$/, '').toLowerCase();
16464
+ if (tabBase === importBase)
16465
+ return name;
16466
+ }
16467
+ return null;
16468
+ }
16469
+ _setTabModified(name, modified) {
16470
+ const tab = this._openedTabs[name];
16471
+ if (!tab || tab.modified === modified)
16472
+ return;
16473
+ tab.modified = modified;
16474
+ const tabEl = this.tabs?.tabDOMs?.[name];
16475
+ if (tabEl)
16476
+ tabEl.toggleAttribute('data-modified', modified);
16477
+ }
16239
16478
  _onSelectTab(isNewTabButton, event, name) {
16240
16479
  if (this.disableEdition) {
16241
16480
  return;
@@ -16461,7 +16700,6 @@ class CodeEditor {
16461
16700
  const lineText = this.doc.getLine(lineIndex);
16462
16701
  const result = Tokenizer.tokenizeLine(lineText, this.language, prevState);
16463
16702
  const langClass = this.language.name.toLowerCase().replace(/[^a-z]/g, '');
16464
- const URL_REGEX = /(https?:\/\/[^\s"'<>)\]]+)/g;
16465
16703
  // Pre-compute which token index gets the bracket-highlight class
16466
16704
  let bracketTokenIdx = -1;
16467
16705
  if (lineIndex === this._bracketOpenLine) {
@@ -16489,15 +16727,20 @@ class CodeEditor {
16489
16727
  const cls = TOKEN_CLASS_MAP[token.type];
16490
16728
  const tokenCol = colOffset;
16491
16729
  colOffset += token.value.length;
16730
+ // Inject content depending on type of token: color, url, path?
16492
16731
  let content;
16493
16732
  if (token.type === 'comment') {
16494
- // Escape then inject clickable URL spans
16495
16733
  const escaped = token.value
16496
16734
  .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
16497
16735
  content = escaped.replace(URL_REGEX, `<span class="code-link" data-url="$1">$1</span>`);
16498
16736
  }
16737
+ else if (token.type === 'string' && isImportPath(result.tokens, ti)) {
16738
+ const inner = token.value.slice(1, -1); // strip surrounding quotes
16739
+ const q = token.value[0];
16740
+ const escapedInner = inner.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
16741
+ content = `${q}<span class="code-path" data-path="${inner}">${escapedInner}</span>${q}`;
16742
+ }
16499
16743
  else {
16500
- // Escape and inject color swatches for hex color strings
16501
16744
  content = injectColorSpans(token.value, lineIndex, tokenCol);
16502
16745
  }
16503
16746
  const bracketClass = ti === bracketTokenIdx ? ' code-bracket-active' : '';
@@ -16767,6 +17010,8 @@ class CodeEditor {
16767
17010
  e.preventDefault();
16768
17011
  if (this.onSave) {
16769
17012
  this.onSave(this.getText(), this);
17013
+ this.undoManager.markSaved();
17014
+ this._setTabModified(this.currentTab.name, false);
16770
17015
  }
16771
17016
  return;
16772
17017
  case 'z':
@@ -16789,6 +17034,17 @@ class CodeEditor {
16789
17034
  e.preventDefault();
16790
17035
  this._doPaste();
16791
17036
  return;
17037
+ case 'home':
17038
+ e.preventDefault();
17039
+ this.cursorSet.set(0, 0);
17040
+ this._afterCursorMove();
17041
+ return;
17042
+ case 'end':
17043
+ e.preventDefault();
17044
+ const lastLine = this.doc.lineCount - 1;
17045
+ this.cursorSet.set(lastLine, this.doc.getLine(lastLine).length);
17046
+ this._afterCursorMove();
17047
+ return;
16792
17048
  case ' ':
16793
17049
  e.preventDefault();
16794
17050
  // Also call user callback if provided
@@ -17561,6 +17817,8 @@ class CodeEditor {
17561
17817
  }
17562
17818
  this._rebuildLines();
17563
17819
  this._afterCursorMove();
17820
+ if (this.currentTab)
17821
+ this._setTabModified(this.currentTab.name, this.undoManager.isModified());
17564
17822
  }
17565
17823
  }
17566
17824
  _doRedo() {
@@ -17572,6 +17830,8 @@ class CodeEditor {
17572
17830
  }
17573
17831
  this._rebuildLines();
17574
17832
  this._afterCursorMove();
17833
+ if (this.currentTab)
17834
+ this._setTabModified(this.currentTab.name, this.undoManager.isModified());
17575
17835
  }
17576
17836
  }
17577
17837
  // Mouse input events:
@@ -17582,7 +17842,7 @@ class CodeEditor {
17582
17842
  return;
17583
17843
  if (this.autocomplete && this.autocomplete.contains(e.target))
17584
17844
  return;
17585
- // Ctrl+click: open link if cursor is over a code-link span
17845
+ // Ctrl+click: open link or import path
17586
17846
  if (e.ctrlKey && e.button === 0) {
17587
17847
  const target = e.target;
17588
17848
  const link = target.closest('.code-link');
@@ -17590,6 +17850,15 @@ class CodeEditor {
17590
17850
  window.open(link.dataset.url, '_blank');
17591
17851
  return;
17592
17852
  }
17853
+ const pathEl = target.closest('.code-path');
17854
+ if (pathEl?.dataset.path) {
17855
+ const rawPath = pathEl.dataset.path;
17856
+ const tabName = this._findTabByPath(rawPath);
17857
+ if (tabName)
17858
+ this.loadTab(tabName);
17859
+ this.onOpenPath?.(rawPath, this);
17860
+ return;
17861
+ }
17593
17862
  }
17594
17863
  e.preventDefault(); // Prevent browser from stealing focus from _inputArea
17595
17864
  this._wasPaired = false;
@@ -17637,18 +17906,55 @@ class CodeEditor {
17637
17906
  }
17638
17907
  this._afterCursorMove();
17639
17908
  this._inputArea.focus();
17640
- // Track mouse for drag selection
17641
- const onMouseMove = (me) => {
17642
- const mx = me.clientX - rect.left - this.xPadding;
17643
- const my = me.clientY - rect.top;
17644
- const ml = Math.max(0, Math.min(Math.floor(my / this.lineHeight), this.doc.lineCount - 1));
17645
- const mc = Math.max(0, Math.min(Math.round(mx / this.charWidth), this.doc.getLine(ml).length));
17909
+ // Track mouse for drag selection (with auto-scroll when outside editor window/area)
17910
+ let lastMouseX = 0;
17911
+ let lastMouseY = 0;
17912
+ let rafId = null;
17913
+ const updateSelection = () => {
17914
+ const currentRect = this.codeContainer.getBoundingClientRect();
17915
+ const mx = lastMouseX - currentRect.left - this.xPadding;
17916
+ const my = lastMouseY - currentRect.top;
17917
+ const ml = LX.clamp(Math.floor(my / this.lineHeight), 0, this.doc.lineCount - 1);
17918
+ const mc = LX.clamp(Math.round(mx / this.charWidth), 0, this.doc.getLine(ml).length);
17646
17919
  const sel = this.cursorSet.getPrimary();
17647
17920
  sel.head = { line: ml, col: mc };
17648
17921
  this._renderCursors();
17649
17922
  this._renderSelections();
17650
17923
  };
17924
+ const autoScroll = () => {
17925
+ const scrollerRect = this.codeScroller.getBoundingClientRect();
17926
+ const overshootY = lastMouseY < scrollerRect.top ? lastMouseY - scrollerRect.top
17927
+ : lastMouseY > scrollerRect.bottom ? lastMouseY - scrollerRect.bottom : 0;
17928
+ const overshootX = lastMouseX < scrollerRect.left ? lastMouseX - scrollerRect.left
17929
+ : lastMouseX > scrollerRect.right ? lastMouseX - scrollerRect.right : 0;
17930
+ if (overshootY === 0 && overshootX === 0) {
17931
+ rafId = null;
17932
+ return;
17933
+ }
17934
+ const speedY = Math.sign(overshootY) * Math.min(Math.abs(overshootY) * 0.3, 15);
17935
+ const speedX = Math.sign(overshootX) * Math.min(Math.abs(overshootX) * 0.3, 15);
17936
+ this.codeScroller.scrollTop += speedY;
17937
+ this.codeScroller.scrollLeft += speedX;
17938
+ this._syncScrollBars();
17939
+ updateSelection();
17940
+ rafId = requestAnimationFrame(autoScroll);
17941
+ };
17942
+ const onMouseMove = (me) => {
17943
+ lastMouseX = me.clientX;
17944
+ lastMouseY = me.clientY;
17945
+ updateSelection();
17946
+ const scrollerRect = this.codeScroller.getBoundingClientRect();
17947
+ const isOutside = me.clientY < scrollerRect.top || me.clientY > scrollerRect.bottom
17948
+ || me.clientX < scrollerRect.left || me.clientX > scrollerRect.right;
17949
+ if (isOutside && rafId === null) {
17950
+ rafId = requestAnimationFrame(autoScroll);
17951
+ }
17952
+ };
17651
17953
  const onMouseUp = () => {
17954
+ if (rafId !== null) {
17955
+ cancelAnimationFrame(rafId);
17956
+ rafId = null;
17957
+ }
17652
17958
  document.removeEventListener('mousemove', onMouseMove);
17653
17959
  document.removeEventListener('mouseup', onMouseUp);
17654
17960
  };
@@ -17709,7 +18015,10 @@ class CodeEditor {
17709
18015
  const suggestions = [];
17710
18016
  const added = new Set();
17711
18017
  const addSuggestion = (s) => {
17712
- if (!added.has(s.label)) {
18018
+ if (added.has(s.label)) {
18019
+ suggestions[suggestions.findIndex(x => x.label === s.label)] = s;
18020
+ }
18021
+ else {
17713
18022
  suggestions.push(s);
17714
18023
  added.add(s.label);
17715
18024
  }
@@ -17722,9 +18031,12 @@ class CodeEditor {
17722
18031
  return suggestion.label.toLowerCase().startsWith(w);
17723
18032
  };
17724
18033
  // Get first suggestions from symbol table
18034
+ const _skipKinds = new Set(['constructor-call', 'method-call']);
17725
18035
  const allSymbols = this.symbolTable.getAllSymbols();
17726
18036
  for (const symbol of allSymbols) {
17727
- const s = { label: symbol.name, kind: symbol.kind, scope: symbol.scope, detail: `${symbol.kind} in ${symbol.scope}` };
18037
+ if (_skipKinds.has(symbol.kind))
18038
+ continue;
18039
+ const s = { label: symbol.name, kind: symbol.kind, scope: symbol.scope };
17728
18040
  if (filterSuggestion(s, word))
17729
18041
  addSuggestion(s);
17730
18042
  }
@@ -17758,7 +18070,7 @@ class CodeEditor {
17758
18070
  // Render suggestions
17759
18071
  suggestions.forEach((suggestion, index) => {
17760
18072
  const item = document.createElement('pre');
17761
- item.insertText = suggestion.insertText ?? suggestion.label;
18073
+ item.suggestionData = suggestion;
17762
18074
  if (index === this._selectedAutocompleteIndex)
17763
18075
  item.classList.add('selected');
17764
18076
  const currSuggestionLabel = suggestion.label;
@@ -17787,11 +18099,15 @@ class CodeEditor {
17787
18099
  break;
17788
18100
  case 'type':
17789
18101
  iconName = 'Type';
17790
- iconClass = 'text-teal-500';
18102
+ iconClass = 'text-purple-500';
17791
18103
  break;
17792
18104
  case 'function':
17793
18105
  iconName = 'Function';
17794
- iconClass = 'text-purple-500';
18106
+ iconClass = 'text-teal-500';
18107
+ break;
18108
+ case 'constant':
18109
+ iconName = 'Pi';
18110
+ iconClass = 'text-rose-600';
17795
18111
  break;
17796
18112
  case 'method':
17797
18113
  iconName = 'Box';
@@ -17805,14 +18121,6 @@ class CodeEditor {
17805
18121
  iconName = 'Layers';
17806
18122
  iconClass = 'text-blue-300';
17807
18123
  break;
17808
- case 'constructor-call':
17809
- iconName = 'Hammer';
17810
- iconClass = 'text-green-500';
17811
- break;
17812
- case 'method-call':
17813
- iconName = 'Parentheses';
17814
- iconClass = 'text-gray-400';
17815
- break;
17816
18124
  }
17817
18125
  item.appendChild(LX.makeIcon(iconName, { iconClass: 'ml-1 mr-2', svgClass: 'sm ' + iconClass }));
17818
18126
  // Highlight the written part
@@ -17827,7 +18135,13 @@ class CodeEditor {
17827
18135
  var postWord = document.createElement('span');
17828
18136
  postWord.textContent = currSuggestionLabel.substring(hIndex + word.length);
17829
18137
  item.appendChild(postWord);
17830
- if (suggestion.kind) {
18138
+ if (suggestion.detail) {
18139
+ const detail = document.createElement('span');
18140
+ detail.textContent = ` ${suggestion.detail}`;
18141
+ detail.className = 'kind text-muted-foreground text-xs! ml-2';
18142
+ item.appendChild(detail);
18143
+ }
18144
+ else if (suggestion.kind) {
17831
18145
  const kind = document.createElement('span');
17832
18146
  kind.textContent = ` (${suggestion.kind})`;
17833
18147
  kind.className = 'kind text-muted-foreground text-xs! ml-2';
@@ -17873,9 +18187,12 @@ class CodeEditor {
17873
18187
  * Insert the selected autocomplete word at cursor.
17874
18188
  */
17875
18189
  _doAutocompleteWord() {
17876
- const text = this._getSelectedAutoCompleteWord();
17877
- if (!text)
18190
+ const suggestion = this._getSelectedAutoCompleteSuggestion();
18191
+ if (!suggestion)
17878
18192
  return;
18193
+ const text = suggestion.insertText ?? suggestion.label;
18194
+ const cursorOffset = suggestion.cursorOffset; // only valid in single line autocomplete
18195
+ const selectLength = suggestion.selectLength;
17879
18196
  const cursor = this.cursorSet.getPrimary().head;
17880
18197
  const { start, end } = this._getWordAtCursor();
17881
18198
  const line = cursor.line;
@@ -17887,7 +18204,13 @@ class CodeEditor {
17887
18204
  const insertOp = this.doc.insert(line, start, text);
17888
18205
  const insertedLines = text.split(/\r?\n/);
17889
18206
  if (insertedLines.length === 1) {
17890
- this.cursorSet.set(line, start + text.length);
18207
+ const cursorCol = start + (cursorOffset ?? text.length);
18208
+ this.cursorSet.set(line, cursorCol);
18209
+ if (selectLength) {
18210
+ const sel = this.cursorSet.getPrimary();
18211
+ sel.anchor = { line, col: cursorCol };
18212
+ sel.head = { line, col: cursorCol + selectLength };
18213
+ }
17891
18214
  }
17892
18215
  else {
17893
18216
  this.cursorSet.set(line + insertedLines.length - 1, insertedLines[insertedLines.length - 1].length);
@@ -17898,11 +18221,11 @@ class CodeEditor {
17898
18221
  this._afterCursorMove();
17899
18222
  this._doHideAutocomplete();
17900
18223
  }
17901
- _getSelectedAutoCompleteWord() {
18224
+ _getSelectedAutoCompleteSuggestion() {
17902
18225
  if (!this.autocomplete || !this._isAutoCompleteActive)
17903
18226
  return null;
17904
18227
  const pre = this.autocomplete.childNodes[this._selectedAutocompleteIndex];
17905
- return pre.insertText;
18228
+ return pre.suggestionData;
17906
18229
  }
17907
18230
  _afterCursorMove() {
17908
18231
  this._renderCursors();
@@ -18143,6 +18466,8 @@ class CodeEditor {
18143
18466
  this._hoverWord = '';
18144
18467
  }
18145
18468
  _onCodeAreaMouseMove(e) {
18469
+ if (!this.currentTab)
18470
+ return;
18146
18471
  // Only show hover when no button is pressed (no dragging)
18147
18472
  if (e.buttons !== 0) {
18148
18473
  this._clearHoverPopup();
@@ -19321,7 +19646,7 @@ class AssetView {
19321
19646
  }
19322
19647
  }
19323
19648
  else if (isFolder) {
19324
- that._enterFolder(item);
19649
+ that._requestEnterFolder(item);
19325
19650
  return;
19326
19651
  }
19327
19652
  const onSelect = that._callbacks['select'];
@@ -19539,17 +19864,17 @@ class AssetView {
19539
19864
  if (!this.prevData.length || !this.currentFolder)
19540
19865
  return;
19541
19866
  this.nextData.push(this.currentFolder);
19542
- this._enterFolder(this.prevData.pop(), false);
19867
+ this._enterFolder(this.prevData.pop(), false, true);
19543
19868
  }, { buttonClass: 'ghost', title: 'Go Back', tooltip: true, icon: 'ArrowLeft' });
19544
19869
  panel.addButton(null, 'GoForwardButton', () => {
19545
19870
  if (!this.nextData.length || !this.currentFolder)
19546
19871
  return;
19547
- this._enterFolder(this.nextData.pop());
19872
+ this._enterFolder(this.nextData.pop(), false, true);
19548
19873
  }, { buttonClass: 'ghost', title: 'Go Forward', tooltip: true, icon: 'ArrowRight' });
19549
19874
  panel.addButton(null, 'GoUpButton', () => {
19550
19875
  const parentFolder = this.currentFolder?.parent;
19551
19876
  if (parentFolder)
19552
- this._enterFolder(parentFolder);
19877
+ this._enterFolder(parentFolder, false, true);
19553
19878
  }, { buttonClass: 'ghost', title: 'Go Upper Folder', tooltip: true, icon: 'ArrowUp' });
19554
19879
  panel.addButton(null, 'RefreshButton', () => {
19555
19880
  this._refreshContent(undefined, undefined, true);
@@ -19589,7 +19914,7 @@ class AssetView {
19589
19914
  this._updatePath();
19590
19915
  }
19591
19916
  else {
19592
- this._enterFolder(node.type === 'folder' ? node : node.parent);
19917
+ this._requestEnterFolder(node.type === 'folder' ? node : node.parent);
19593
19918
  this._previewAsset(node);
19594
19919
  if (node.type !== 'folder') {
19595
19920
  this.content.querySelectorAll('.lexassetitem').forEach((i) => i.classList.remove('selected'));
@@ -19961,12 +20286,41 @@ class AssetView {
19961
20286
  this.toolsPanel.refresh();
19962
20287
  this._refreshContent();
19963
20288
  }
19964
- async _enterFolder(folderItem, storeCurrent = true) {
20289
+ _requestEnterFolder(folderItem, storeCurrent = true) {
20290
+ if (!folderItem) {
20291
+ return;
20292
+ }
20293
+ const onBeforeEnterFolder = this._callbacks['beforeEnterFolder'];
20294
+ const onEnterFolder = this._callbacks['enterFolder'];
20295
+ const resolve = (...args) => {
20296
+ const child = this.currentData[0];
20297
+ const sameFolder = child?.parent?.metadata?.uid === folderItem.metadata?.uid;
20298
+ const mustRefresh = args[0] || !sameFolder;
20299
+ this._enterFolder(folderItem, storeCurrent, mustRefresh);
20300
+ const event = {
20301
+ type: 'enter-folder',
20302
+ to: folderItem,
20303
+ userInitiated: true
20304
+ };
20305
+ if (onEnterFolder)
20306
+ onEnterFolder(event, ...args);
20307
+ };
20308
+ if (onBeforeEnterFolder) {
20309
+ const event = {
20310
+ type: 'enter-folder',
20311
+ to: folderItem,
20312
+ userInitiated: true
20313
+ };
20314
+ onBeforeEnterFolder(event, resolve);
20315
+ }
20316
+ else {
20317
+ resolve();
20318
+ }
20319
+ }
20320
+ _enterFolder(folderItem, storeCurrent, mustRefresh) {
19965
20321
  if (!folderItem) {
19966
20322
  return;
19967
20323
  }
19968
- const child = this.currentData[0];
19969
- const sameFolder = child?.parent?.metadata?.uid === folderItem.metadata?.uid;
19970
20324
  if (storeCurrent) {
19971
20325
  this.prevData.push(this.currentFolder ?? {
19972
20326
  id: '/',
@@ -19975,17 +20329,6 @@ class AssetView {
19975
20329
  metadata: {}
19976
20330
  });
19977
20331
  }
19978
- let mustRefresh = !sameFolder;
19979
- const onEnterFolder = this._callbacks['enterFolder'];
19980
- if (onEnterFolder !== undefined) {
19981
- const event = {
19982
- type: 'enter_folder',
19983
- to: folderItem,
19984
- userInitiated: true
19985
- };
19986
- const r = await onEnterFolder(event);
19987
- mustRefresh = mustRefresh || r;
19988
- }
19989
20332
  // Update this after the event since the user might have added or modified the data
19990
20333
  this.currentFolder = folderItem;
19991
20334
  this.currentData = this.currentFolder?.children ?? [];