codexly-ui 0.14.3 → 0.14.4

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.
@@ -4505,6 +4505,12 @@ class ClxNumberComponent {
4505
4505
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
4506
4506
  // ── Internal state ───────────────────────────────────────────────────────
4507
4507
  _value = signal(0, ...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
4508
+ /** What the native input actually shows while the user is typing — kept separate from _value so
4509
+ * a trailing "." or "," (valid mid-edit, not yet a complete number) isn't stomped by re-rendering
4510
+ * the parsed numeric value on every keystroke; see _handleInput's own comment. Resynced from
4511
+ * _value only on programmatic changes (writeValue, the value() input, +/-, blur), never while the
4512
+ * user is actively typing a valid partial number. */
4513
+ _displayText = signal('0', ...(ngDevMode ? [{ debugName: "_displayText" }] : /* istanbul ignore next */ []));
4508
4514
  _cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_cvaDisabled" }] : /* istanbul ignore next */ []));
4509
4515
  _cardDisabled = inject(CLX_CARD_DISABLED_CONTEXT, { optional: true });
4510
4516
  _cvaConnected = false;
@@ -4512,7 +4518,7 @@ class ClxNumberComponent {
4512
4518
  _valueEffect = effect(() => {
4513
4519
  const v = this.value();
4514
4520
  if (!this._cvaConnected)
4515
- untracked(() => this._value.set(v));
4521
+ untracked(() => { this._value.set(v); this._displayText.set(String(v)); });
4516
4522
  }, ...(ngDevMode ? [{ debugName: "_valueEffect" }] : /* istanbul ignore next */ []));
4517
4523
  _onChange = () => { };
4518
4524
  _onTouched = () => { };
@@ -4557,6 +4563,7 @@ class ClxNumberComponent {
4557
4563
  writeValue(val) {
4558
4564
  this._cvaConnected = true;
4559
4565
  this._value.set(val ?? 0);
4566
+ this._displayText.set(String(val ?? 0));
4560
4567
  }
4561
4568
  registerOnChange(fn) {
4562
4569
  this._onChange = fn;
@@ -4568,9 +4575,44 @@ class ClxNumberComponent {
4568
4575
  this._cvaDisabled.set(disabled);
4569
4576
  }
4570
4577
  // ── Handlers ─────────────────────────────────────────────────────────────
4578
+ /** Allows only digits, a single decimal separator (. or ,) and a leading minus — anything else
4579
+ * typed (letters, a second dot, a stray minus mid-string) is dropped. This is the type="text"
4580
+ * replacement for the browser's own type="number" filtering, needed because the native one keys
4581
+ * off the BROWSER's locale for which decimal separator it accepts and silently rejects the other,
4582
+ * blocking that key outright for users whose input doesn't match. */
4583
+ _sanitizeTyped(raw) {
4584
+ let out = '';
4585
+ let seenDot = false;
4586
+ for (let i = 0; i < raw.length; i++) {
4587
+ const ch = raw[i];
4588
+ if (ch === '-' && i === 0) {
4589
+ out += ch;
4590
+ continue;
4591
+ }
4592
+ if ((ch === '.' || ch === ',') && !seenDot) {
4593
+ seenDot = true;
4594
+ out += '.';
4595
+ continue;
4596
+ }
4597
+ if (ch >= '0' && ch <= '9') {
4598
+ out += ch;
4599
+ }
4600
+ }
4601
+ return out;
4602
+ }
4571
4603
  _handleInput(event) {
4572
- const raw = event.target.value;
4573
- const num = raw === '' ? 0 : parseFloat(raw);
4604
+ const input = event.target;
4605
+ const sanitized = this._sanitizeTyped(input.value);
4606
+ if (sanitized !== input.value)
4607
+ input.value = sanitized;
4608
+ this._displayText.set(sanitized);
4609
+ // A bare "-", "." or a trailing "." (e.g. "2.") is a valid mid-edit state but not yet a
4610
+ // complete number — don't push it through onChange/clamping until it parses, and don't touch
4611
+ // _value (which would re-render _displayText via nothing, since nothing reads _value for the
4612
+ // input anymore, but would desync the two if left stale).
4613
+ if (sanitized === '' || sanitized === '-' || sanitized.endsWith('.'))
4614
+ return;
4615
+ const num = parseFloat(sanitized);
4574
4616
  if (isNaN(num))
4575
4617
  return;
4576
4618
  let clamped = num;
@@ -4580,20 +4622,23 @@ class ClxNumberComponent {
4580
4622
  clamped = Math.min(clamped, this.max());
4581
4623
  this._value.set(clamped);
4582
4624
  this._onChange(clamped);
4583
- // Reflect the clamp back into the native input immediately — otherwise the DOM would keep
4584
- // showing the raw out-of-range digits the user typed until blur re-renders the value.
4625
+ // Reflect the clamp back into both the native input and _displayText immediately — otherwise
4626
+ // the DOM would keep showing the raw out-of-range digits the user typed until blur.
4585
4627
  if (clamped !== num) {
4586
- event.target.value = String(clamped);
4628
+ input.value = String(clamped);
4629
+ this._displayText.set(String(clamped));
4587
4630
  }
4588
4631
  }
4589
4632
  _handleBlur() {
4590
- // Clamp to min/max on blur
4633
+ // Clamp to min/max on blur, and resolve any incomplete typed state (trailing ".", bare "-")
4634
+ // back to the last valid numeric value.
4591
4635
  let v = this._value();
4592
4636
  if (this.min() !== null)
4593
4637
  v = Math.max(v, this.min());
4594
4638
  if (this.max() !== null)
4595
4639
  v = Math.min(v, this.max());
4596
4640
  this._value.set(v);
4641
+ this._displayText.set(String(v));
4597
4642
  this._onChange(v);
4598
4643
  this._onTouched();
4599
4644
  }
@@ -4621,6 +4666,7 @@ class ClxNumberComponent {
4621
4666
  }
4622
4667
  _applyValue(v) {
4623
4668
  this._value.set(v);
4669
+ this._displayText.set(String(v));
4624
4670
  this._onChange(v);
4625
4671
  }
4626
4672
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxNumberComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -4650,16 +4696,17 @@ class ClxNumberComponent {
4650
4696
  </div>
4651
4697
  }
4652
4698
 
4653
- <!-- Native number input -->
4699
+ <!-- type="text" + inputmode="decimal" instead of type="number": a native number input
4700
+ rejects a comma decimal separator outright (event.target.value silently becomes '' while
4701
+ the typed text is invalid), which stomps whatever the user typed on every keystroke and
4702
+ resets the caret to 0 — see _handleInput's own comment for the parse side of this fix. -->
4654
4703
  <input
4655
- type="number"
4704
+ type="text"
4705
+ inputmode="decimal"
4656
4706
  [id]="_inputId"
4657
4707
  [name]="_inputId"
4658
4708
  [class]="_inputClass()"
4659
- [value]="_value()"
4660
- [min]="min() ?? ''"
4661
- [max]="max() ?? ''"
4662
- [step]="step()"
4709
+ [value]="_displayText()"
4663
4710
  [disabled]="_disabled()"
4664
4711
  [placeholder]="placeholder()"
4665
4712
  (input)="_handleInput($event)"
@@ -4767,16 +4814,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
4767
4814
  </div>
4768
4815
  }
4769
4816
 
4770
- <!-- Native number input -->
4817
+ <!-- type="text" + inputmode="decimal" instead of type="number": a native number input
4818
+ rejects a comma decimal separator outright (event.target.value silently becomes '' while
4819
+ the typed text is invalid), which stomps whatever the user typed on every keystroke and
4820
+ resets the caret to 0 — see _handleInput's own comment for the parse side of this fix. -->
4771
4821
  <input
4772
- type="number"
4822
+ type="text"
4823
+ inputmode="decimal"
4773
4824
  [id]="_inputId"
4774
4825
  [name]="_inputId"
4775
4826
  [class]="_inputClass()"
4776
- [value]="_value()"
4777
- [min]="min() ?? ''"
4778
- [max]="max() ?? ''"
4779
- [step]="step()"
4827
+ [value]="_displayText()"
4780
4828
  [disabled]="_disabled()"
4781
4829
  [placeholder]="placeholder()"
4782
4830
  (input)="_handleInput($event)"