@kenlo/sds-design-system 1.1.1 → 1.1.2
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.
|
@@ -391,19 +391,50 @@
|
|
|
391
391
|
.sds-input__hint--disabled { color: var(--color-neutral-40); }
|
|
392
392
|
/* ── Textbox (textarea) variant ── */
|
|
393
393
|
.sds-input--textbox {
|
|
394
|
-
height: auto;
|
|
394
|
+
height: var(--sds-textarea-height, auto);
|
|
395
395
|
min-height: var(--sds-textarea-min-height, 114px);
|
|
396
396
|
align-items: flex-start;
|
|
397
|
-
resize: vertical;
|
|
398
397
|
overflow: hidden;
|
|
399
398
|
}
|
|
400
|
-
/* Field: fixed padding-top — guarantees constant height regardless of state
|
|
399
|
+
/* Field: fixed padding-top — guarantees constant height regardless of state.
|
|
400
|
+
The textarea runs down to the bottom border, as in KDLS: past the last row the start of
|
|
401
|
+
the next line shows cut at the edge, so a longer text reads as one that goes on. */
|
|
401
402
|
.sds-input--textbox .sds-input__field {
|
|
402
403
|
min-height: 0;
|
|
403
404
|
padding-top: var(--spacing-7);
|
|
404
|
-
padding-bottom:
|
|
405
|
+
padding-bottom: 0;
|
|
405
406
|
align-self: stretch;
|
|
406
407
|
}
|
|
408
|
+
/* The target grows with the corner radius so the rounded corner never eats into its 24px,
|
|
409
|
+
and the grip moves in with it so it stays inside the curve. */
|
|
410
|
+
.sds-input__resize-handle {
|
|
411
|
+
position: absolute;
|
|
412
|
+
right: 0;
|
|
413
|
+
bottom: 0;
|
|
414
|
+
width: max(var(--spacing-6), calc(var(--radius-md) * 0.5 + var(--spacing-4)));
|
|
415
|
+
height: max(var(--spacing-6), calc(var(--radius-md) * 0.5 + var(--spacing-4)));
|
|
416
|
+
display: flex;
|
|
417
|
+
align-items: flex-end;
|
|
418
|
+
justify-content: flex-end;
|
|
419
|
+
padding: 0 max(var(--spacing-1), calc(var(--radius-md) * 0.3)) max(var(--spacing-1), calc(var(--radius-md) * 0.3)) 0;
|
|
420
|
+
box-sizing: border-box;
|
|
421
|
+
color: var(--color-neutral-50);
|
|
422
|
+
cursor: ns-resize;
|
|
423
|
+
touch-action: none;
|
|
424
|
+
z-index: 1;
|
|
425
|
+
outline: none;
|
|
426
|
+
transition: color 120ms ease;
|
|
427
|
+
}
|
|
428
|
+
.sds-input__resize-handle:hover,
|
|
429
|
+
.sds-input--resizing .sds-input__resize-handle {
|
|
430
|
+
color: var(--color-primary-50-solid);
|
|
431
|
+
}
|
|
432
|
+
.sds-input__resize-handle:focus-visible {
|
|
433
|
+
box-shadow: var(--focus-ring-inset);
|
|
434
|
+
}
|
|
435
|
+
.sds-input--resizing {
|
|
436
|
+
user-select: none;
|
|
437
|
+
}
|
|
407
438
|
.sds-input--textbox .sds-input__label {
|
|
408
439
|
top: 26px;
|
|
409
440
|
transform: translateY(-50%);
|
|
@@ -420,14 +451,22 @@
|
|
|
420
451
|
font-size: var(--font-size-label-sm);
|
|
421
452
|
line-height: var(--lh-label-sm);
|
|
422
453
|
}
|
|
454
|
+
/* The textarea also runs over half of the field's right padding, so its scrollbar sits 8px
|
|
455
|
+
from the right border instead of 16px inside the text's room, as in KDLS. The gutter is
|
|
456
|
+
kept even without a bar, so the text ends at the same place with or without one. Its own
|
|
457
|
+
8px at the end is the bottom room, reached when it scrolls to the end. */
|
|
423
458
|
.sds-input__native--textarea {
|
|
459
|
+
box-sizing: border-box;
|
|
424
460
|
padding-top: 0;
|
|
425
|
-
padding-
|
|
461
|
+
padding-right: 0;
|
|
462
|
+
padding-bottom: var(--spacing-2);
|
|
426
463
|
resize: none;
|
|
427
464
|
flex: 1;
|
|
428
465
|
line-height: var(--lh-body-md);
|
|
429
466
|
overflow-y: auto;
|
|
430
|
-
|
|
467
|
+
scrollbar-gutter: stable;
|
|
468
|
+
width: calc(100% + var(--spacing-2));
|
|
469
|
+
margin-right: calc(-1 * var(--spacing-2));
|
|
431
470
|
}
|
|
432
471
|
/* ── Dark mode ── */
|
|
433
472
|
[data-theme="dark"] .sds-input__country {
|
|
@@ -47,6 +47,9 @@ import { assertInputType, assertFeedback, assertWidth, formatValue, nativeTypeFo
|
|
|
47
47
|
import { validateOnFor, SUBMIT_EVENT } from "../Form/form-context.js";
|
|
48
48
|
import { rehyphenate } from "../../foundations/attrs.js";
|
|
49
49
|
|
|
50
|
+
static const TEXTBOX_LINE = 24;
|
|
51
|
+
static const TEXTBOX_CHROME = 42;
|
|
52
|
+
|
|
50
53
|
class {
|
|
51
54
|
onCreate(input) {
|
|
52
55
|
this.state = {
|
|
@@ -56,6 +59,8 @@ class {
|
|
|
56
59
|
showPassword: false,
|
|
57
60
|
error: null,
|
|
58
61
|
touched: false,
|
|
62
|
+
textboxHeight: null,
|
|
63
|
+
resizing: false,
|
|
59
64
|
};
|
|
60
65
|
}
|
|
61
66
|
|
|
@@ -137,9 +142,58 @@ class {
|
|
|
137
142
|
}
|
|
138
143
|
|
|
139
144
|
onDestroy() {
|
|
145
|
+
this.stopResize?.();
|
|
140
146
|
this.getEl("field-root")?.removeEventListener(SUBMIT_EVENT, this.onFormSubmit);
|
|
141
147
|
}
|
|
142
148
|
|
|
149
|
+
get textboxMinHeight() {
|
|
150
|
+
return (this.input.rows ?? 3) * TEXTBOX_LINE + TEXTBOX_CHROME;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
clampTextboxHeight(value) {
|
|
154
|
+
return Math.max(this.textboxMinHeight, Math.round(value));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
currentTextboxHeight() {
|
|
158
|
+
return this.state.textboxHeight
|
|
159
|
+
?? (this.getEl("box")?.getBoundingClientRect().height || this.textboxMinHeight);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
startResize(event) {
|
|
163
|
+
if (event.button !== 0) return;
|
|
164
|
+
event.preventDefault();
|
|
165
|
+
const startY = event.clientY;
|
|
166
|
+
const startHeight = this.currentTextboxHeight();
|
|
167
|
+
this.state.resizing = true;
|
|
168
|
+
|
|
169
|
+
const onMove = (moveEvent) => {
|
|
170
|
+
this.state.textboxHeight = this.clampTextboxHeight(startHeight + moveEvent.clientY - startY);
|
|
171
|
+
};
|
|
172
|
+
const onUp = () => {
|
|
173
|
+
this.stopResize = null;
|
|
174
|
+
this.state.resizing = false;
|
|
175
|
+
document.removeEventListener("pointermove", onMove);
|
|
176
|
+
document.removeEventListener("pointerup", onUp);
|
|
177
|
+
document.removeEventListener("pointercancel", onUp);
|
|
178
|
+
};
|
|
179
|
+
this.stopResize = onUp;
|
|
180
|
+
document.addEventListener("pointermove", onMove);
|
|
181
|
+
document.addEventListener("pointerup", onUp);
|
|
182
|
+
document.addEventListener("pointercancel", onUp);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
resizeByKey(event) {
|
|
186
|
+
const keys = {
|
|
187
|
+
ArrowDown: () => this.currentTextboxHeight() + TEXTBOX_LINE,
|
|
188
|
+
ArrowUp: () => this.currentTextboxHeight() - TEXTBOX_LINE,
|
|
189
|
+
Home: () => this.textboxMinHeight,
|
|
190
|
+
};
|
|
191
|
+
const next = keys[event.key];
|
|
192
|
+
if (!next) return;
|
|
193
|
+
event.preventDefault();
|
|
194
|
+
this.state.textboxHeight = this.clampTextboxHeight(next());
|
|
195
|
+
}
|
|
196
|
+
|
|
143
197
|
get currentValue() {
|
|
144
198
|
return this.input.value !== undefined ? this.input.value : this.state.value;
|
|
145
199
|
}
|
|
@@ -306,6 +360,7 @@ $ {
|
|
|
306
360
|
// KDLS marks only the textbox. A modifier per type produced ten classes no rule
|
|
307
361
|
// matched — dead weight in every field's class attribute.
|
|
308
362
|
type === "textbox" ? "sds-input--textbox" : "",
|
|
363
|
+
state.resizing ? "sds-input--resizing" : "",
|
|
309
364
|
effectiveFeedback !== "none" ? `sds-input--feedback-${effectiveFeedback}` : "",
|
|
310
365
|
disabled ? "sds-input--disabled" : "",
|
|
311
366
|
loading ? "sds-input--loading" : "",
|
|
@@ -348,9 +403,13 @@ $ {
|
|
|
348
403
|
// that reads it and lost the line that writes it. 24px a line plus the 42px the label
|
|
349
404
|
// and padding take, which is KDLS's arithmetic.
|
|
350
405
|
<div
|
|
406
|
+
key="box"
|
|
351
407
|
class=fieldBoxClass
|
|
352
408
|
aria-busy=(loading ? "true" : null)
|
|
353
|
-
style=(isTextarea
|
|
409
|
+
style=(isTextarea
|
|
410
|
+
? `--sds-textarea-min-height: ${component.textboxMinHeight}px`
|
|
411
|
+
+ (state.textboxHeight !== null ? `; --sds-textarea-height: ${state.textboxHeight}px` : "")
|
|
412
|
+
: null)
|
|
354
413
|
>
|
|
355
414
|
<if(showsCountry)>
|
|
356
415
|
<!-- The flag opens the list and the dial code sits beside it, which is the whole
|
|
@@ -465,6 +524,24 @@ $ {
|
|
|
465
524
|
</button>
|
|
466
525
|
</if>
|
|
467
526
|
|
|
527
|
+
<if(isTextarea && !disabled)>
|
|
528
|
+
<div
|
|
529
|
+
class="sds-input__resize-handle"
|
|
530
|
+
role="separator"
|
|
531
|
+
aria-orientation="horizontal"
|
|
532
|
+
aria-label="Redimensionar campo"
|
|
533
|
+
aria-valuemin=component.textboxMinHeight
|
|
534
|
+
aria-valuenow=(state.textboxHeight ?? component.textboxMinHeight)
|
|
535
|
+
tabindex="0"
|
|
536
|
+
on-pointerdown("startResize")
|
|
537
|
+
on-keydown("resizeByKey")
|
|
538
|
+
>
|
|
539
|
+
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" aria-hidden="true">
|
|
540
|
+
<path d="M9 1L1 9M9 5L5 9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
|
541
|
+
</svg>
|
|
542
|
+
</div>
|
|
543
|
+
</if>
|
|
544
|
+
|
|
468
545
|
<if(showsIncrementer)>
|
|
469
546
|
<div class="sds-input__spinner">
|
|
470
547
|
<button type="button" class="sds-input__spinner-btn" tabindex="-1"
|
package/package.json
CHANGED