codexly-ui 0.14.2 → 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.
- package/fesm2022/codexly-ui.mjs +80 -19
- package/fesm2022/codexly-ui.mjs.map +1 -1
- package/package.json +1 -1
- package/types/codexly-ui.d.ts +25 -13
package/fesm2022/codexly-ui.mjs
CHANGED
|
@@ -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
|
|
4573
|
-
const
|
|
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
|
|
4584
|
-
// showing the raw out-of-range digits the user typed until blur
|
|
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
|
-
|
|
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
|
-
<!--
|
|
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="
|
|
4704
|
+
type="text"
|
|
4705
|
+
inputmode="decimal"
|
|
4656
4706
|
[id]="_inputId"
|
|
4657
4707
|
[name]="_inputId"
|
|
4658
4708
|
[class]="_inputClass()"
|
|
4659
|
-
[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
|
-
<!--
|
|
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="
|
|
4822
|
+
type="text"
|
|
4823
|
+
inputmode="decimal"
|
|
4773
4824
|
[id]="_inputId"
|
|
4774
4825
|
[name]="_inputId"
|
|
4775
4826
|
[class]="_inputClass()"
|
|
4776
|
-
[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)"
|
|
@@ -9412,6 +9460,11 @@ class ClxModalService {
|
|
|
9412
9460
|
{ provide: CLX_MODAL_SIZE, useValue: size },
|
|
9413
9461
|
],
|
|
9414
9462
|
});
|
|
9463
|
+
// The element that had focus right before opening (typically the button that triggered
|
|
9464
|
+
// this modal) — its own hover/focus tooltip, if any, never receives a real blur otherwise:
|
|
9465
|
+
// focus stays parked on it for as long as the modal is open, so switching OS windows while
|
|
9466
|
+
// it's open can leave a hover-triggered tooltip stuck with no blur/mouseleave to dismiss it.
|
|
9467
|
+
const triggerEl = document.activeElement;
|
|
9415
9468
|
const { compRef, overlayRef } = this._overlay.mount(component, innerInjector, {
|
|
9416
9469
|
zIndex: 1000,
|
|
9417
9470
|
backdrop: hasBackdrop,
|
|
@@ -9421,6 +9474,11 @@ class ClxModalService {
|
|
|
9421
9474
|
panelEl.style.width = `min(calc(100vw - 2rem), ${maxWidth})`;
|
|
9422
9475
|
panelEl.style.maxWidth = maxWidth;
|
|
9423
9476
|
panelEl.style.maxHeight = '90vh';
|
|
9477
|
+
// Move focus into the modal so the trigger element actually blurs (see triggerEl above).
|
|
9478
|
+
// tabIndex -1 + outline:none keeps this a pure focus target, not a visible/tabbable stop.
|
|
9479
|
+
panelEl.tabIndex = -1;
|
|
9480
|
+
panelEl.style.outline = 'none';
|
|
9481
|
+
panelEl.focus();
|
|
9424
9482
|
// Enter animation
|
|
9425
9483
|
this._animate.animate(panelEl, {
|
|
9426
9484
|
animation: animationIn,
|
|
@@ -9438,6 +9496,9 @@ class ClxModalService {
|
|
|
9438
9496
|
fillMode: 'forwards',
|
|
9439
9497
|
}).then(() => {
|
|
9440
9498
|
overlayRef.destroy();
|
|
9499
|
+
// Restore focus to whatever opened this modal — standard modal a11y practice, and what
|
|
9500
|
+
// lets that element's tooltip (if any) receive a real blur instead of staying stuck.
|
|
9501
|
+
triggerEl?.focus?.();
|
|
9441
9502
|
_resolve(result);
|
|
9442
9503
|
});
|
|
9443
9504
|
};
|