@skra/ui 0.9.0 → 0.9.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.
@@ -72,6 +72,7 @@ Violating these is a review blocker in a consumer repo just as inside Skra.
72
72
  | ----------------------------------------------------------------------- | ----------------------------------------- |
73
73
  | Add `@skra/ui/*` to a project and import an atom | `setup-and-gotchas.md` |
74
74
  | Bind a text input / textarea to a form | `forms-and-binding.md` |
75
+ | Size a textarea, let it grow with content, cap its height | `forms-and-binding.md` (sizing) |
75
76
  | Bind a `skra-select` / `skra-datepicker` / `skra-star-rating` to a form | `forms-and-binding.md` (the value bridge) |
76
77
  | Name an icon-only button | `accessible-names.md` |
77
78
  | Switch skin / dark mode at runtime | `theming-and-encapsulation.md` |
@@ -1,7 +1,7 @@
1
1
  # Forms & Value Binding
2
2
 
3
3
  > Load this for: wiring a Skra form atom to **Signal Forms**, **Reactive Forms**, or
4
- > **`ngModel`**. The governing rule is ADR 037: **Skra atoms are agnostic — you own the
4
+ > **`ngModel`**, or sizing a textarea (`rows`, `[skraAutoResize]`, a height cap). The governing rule is ADR 037: **Skra atoms are agnostic — you own the
5
5
  > value and pick the binding strategy.** No atom implements `ControlValueAccessor` /
6
6
  > `FormValueControl`.
7
7
 
@@ -93,6 +93,36 @@ price of the atom staying framework-agnostic. If it recurs across many fields, s
93
93
  > Reminder: there is **no** `<skra-checkbox>`/`<skra-radio>` element — those are the
94
94
  > class-A native-host directives above. `skra-counter` is not an input either (see below).
95
95
 
96
+ ## Sizing a `textarea[skraTextarea]`
97
+
98
+ Use the native `rows` for the starting height. The resize handle works in the **block axis
99
+ only**, so the field never drags sideways out of its column. If a field really needs both axes,
100
+ declare `resize: both` in your own component styles, which outrank Skra's without `!important`.
101
+
102
+ `[skraAutoResize]` makes the field grow with its content, and only ever adds height:
103
+
104
+ - **`rows` stays the minimum** (`rows` 1–10; with no `rows`, two lines, as natively). An
105
+ auto-resize field without `rows` used to start at one line; write `rows="1"` for that.
106
+ - **No resize handle** wherever the browser can grow the field (`field-sizing`). A drag would pin
107
+ a fixed height and stop the growth. Where it cannot, the block-axis handle stays.
108
+ - **The maximum is yours.** Skra sets none, because how tall a field may get depends on its page.
109
+ Cap it with `max-block-size`; past the cap the field scrolls.
110
+
111
+ ```html
112
+ <textarea skraTextarea skraAutoResize rows="3" class="invite"></textarea>
113
+ ```
114
+
115
+ ```css
116
+ .invite {
117
+ max-block-size: 12rem; /* three lines to start, then grow to 12rem, then scroll */
118
+ }
119
+ ```
120
+
121
+ Under `[skraAutoResize]` the textarea is `border-box`, so the cap includes its padding and
122
+ border, and a full-width field stays inside its column with or without an app-wide reset. A
123
+ taller minimum is your own `min-block-size`: in your component styles it outranks Skra's floor
124
+ without `!important`. Globally, write `textarea.x`; a bare `.x` ties with Skra and loses.
125
+
96
126
  ## `skra-counter` — a display atom, not an input
97
127
 
98
128
  `skra-counter` renders a live character count for a sibling control; it does **not** hold
@@ -26,6 +26,10 @@ import { SkraFieldContext } from '@skra/ui/field';
26
26
  *
27
27
  * Native `rows` / `cols` remain the consumer-facing sizing primitives — Forge
28
28
  * does not duplicate them as Angular inputs (boundary #9 native-attr-first).
29
+ * The resize handle works in the block axis only (`resize: block`, GitHub #12);
30
+ * a consumer wanting both axes declares `resize: both` in its own styles. With
31
+ * `[skraAutoResize]` the handle is off and `rows` stays the minimum height; the
32
+ * maximum is the consumer's `max-block-size`.
29
33
  *
30
34
  * `SkraFieldContext` is injected `{ optional: true }` (ADR 013 §3.7) — same
31
35
  * posture as SkraInput / SkraCheckbox. Standalone, the atom renders the skin
@@ -53,7 +57,7 @@ class SkraTextarea {
53
57
  }
54
58
  }
55
59
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraTextarea, deps: [], target: i0.ɵɵFactoryTarget.Component });
56
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.1", type: SkraTextarea, isStandalone: true, selector: "textarea[skraTextarea]", host: { properties: { "attr.aria-describedby": "context?.describedByIds() ?? null", "attr.aria-invalid": "context?.hasError() ? \"true\" : null", "attr.aria-required": "context?.isRequired() ? \"true\" : null" } }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: [":host{background:var(--skra-comp-field-background);color:var(--skra-comp-field-foreground);border-color:var(--skra-comp-field-border-color);border-width:var(--skra-comp-field-border-width);border-style:var(--skra-comp-field-border-style);border-radius:var(--skra-comp-field-radius);padding-block:var(--skra-comp-field-padding-block);padding-inline:var(--skra-comp-field-padding-inline);box-shadow:var(--skra-comp-field-shadow);font:inherit;outline:none;transition:background-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),border-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),box-shadow var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing)}@media(hover:hover)and (pointer:fine){:host(:hover:not(:disabled):not([readonly])){background:var(--skra-comp-field-background-hover);border-color:var(--skra-comp-field-border-color-hover)}}:host(:focus-visible){border-color:var(--skra-comp-field-focus-ring-color);box-shadow:0 0 0 var(--skra-comp-field-focus-ring-width) var(--skra-comp-field-focus-ring-color)}:host(:disabled){cursor:not-allowed;opacity:var(--skra-sys-opacity-disabled)}:host([readonly]){cursor:default}:host([aria-invalid=true]){border-color:var(--skra-comp-field-error-color)}:host([aria-invalid=true]:focus-visible){box-shadow:0 0 0 var(--skra-comp-field-focus-ring-width) var(--skra-comp-field-error-color)}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
60
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.1", type: SkraTextarea, isStandalone: true, selector: "textarea[skraTextarea]", host: { properties: { "attr.aria-describedby": "context?.describedByIds() ?? null", "attr.aria-invalid": "context?.hasError() ? \"true\" : null", "attr.aria-required": "context?.isRequired() ? \"true\" : null" } }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: [":host{background:var(--skra-comp-field-background);color:var(--skra-comp-field-foreground);border-color:var(--skra-comp-field-border-color);border-width:var(--skra-comp-field-border-width);border-style:var(--skra-comp-field-border-style);border-radius:var(--skra-comp-field-radius);padding-block:var(--skra-comp-field-padding-block);padding-inline:var(--skra-comp-field-padding-inline);box-shadow:var(--skra-comp-field-shadow);font:inherit;outline:none;resize:vertical;resize:block;transition:background-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),border-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),box-shadow var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing)}:host(:where([skraAutoResize])){box-sizing:border-box;min-block-size:calc(2lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"1\"])){min-block-size:calc(1lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"2\"])){min-block-size:calc(2lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"3\"])){min-block-size:calc(3lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"4\"])){min-block-size:calc(4lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"5\"])){min-block-size:calc(5lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"6\"])){min-block-size:calc(6lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"7\"])){min-block-size:calc(7lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"8\"])){min-block-size:calc(8lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"9\"])){min-block-size:calc(9lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"10\"])){min-block-size:calc(10lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}@media(hover:hover)and (pointer:fine){:host(:hover:not(:disabled):not([readonly])){background:var(--skra-comp-field-background-hover);border-color:var(--skra-comp-field-border-color-hover)}}:host(:focus-visible){border-color:var(--skra-comp-field-focus-ring-color);box-shadow:0 0 0 var(--skra-comp-field-focus-ring-width) var(--skra-comp-field-focus-ring-color)}:host(:disabled){cursor:not-allowed;opacity:var(--skra-sys-opacity-disabled)}:host([readonly]){cursor:default}:host([aria-invalid=true]){border-color:var(--skra-comp-field-error-color)}:host([aria-invalid=true]:focus-visible){box-shadow:0 0 0 var(--skra-comp-field-focus-ring-width) var(--skra-comp-field-error-color)}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
57
61
  }
58
62
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraTextarea, decorators: [{
59
63
  type: Component,
@@ -67,7 +71,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImpor
67
71
  // `aria-disabled` duplicate. Readonly styling keys off `:read-only`.
68
72
  // Driven imperatively below, NOT as host bindings — see the constructor
69
73
  // note on standalone attribute ownership.
70
- }, styles: [":host{background:var(--skra-comp-field-background);color:var(--skra-comp-field-foreground);border-color:var(--skra-comp-field-border-color);border-width:var(--skra-comp-field-border-width);border-style:var(--skra-comp-field-border-style);border-radius:var(--skra-comp-field-radius);padding-block:var(--skra-comp-field-padding-block);padding-inline:var(--skra-comp-field-padding-inline);box-shadow:var(--skra-comp-field-shadow);font:inherit;outline:none;transition:background-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),border-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),box-shadow var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing)}@media(hover:hover)and (pointer:fine){:host(:hover:not(:disabled):not([readonly])){background:var(--skra-comp-field-background-hover);border-color:var(--skra-comp-field-border-color-hover)}}:host(:focus-visible){border-color:var(--skra-comp-field-focus-ring-color);box-shadow:0 0 0 var(--skra-comp-field-focus-ring-width) var(--skra-comp-field-focus-ring-color)}:host(:disabled){cursor:not-allowed;opacity:var(--skra-sys-opacity-disabled)}:host([readonly]){cursor:default}:host([aria-invalid=true]){border-color:var(--skra-comp-field-error-color)}:host([aria-invalid=true]:focus-visible){box-shadow:0 0 0 var(--skra-comp-field-focus-ring-width) var(--skra-comp-field-error-color)}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n"] }]
74
+ }, styles: [":host{background:var(--skra-comp-field-background);color:var(--skra-comp-field-foreground);border-color:var(--skra-comp-field-border-color);border-width:var(--skra-comp-field-border-width);border-style:var(--skra-comp-field-border-style);border-radius:var(--skra-comp-field-radius);padding-block:var(--skra-comp-field-padding-block);padding-inline:var(--skra-comp-field-padding-inline);box-shadow:var(--skra-comp-field-shadow);font:inherit;outline:none;resize:vertical;resize:block;transition:background-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),border-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),box-shadow var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing)}:host(:where([skraAutoResize])){box-sizing:border-box;min-block-size:calc(2lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"1\"])){min-block-size:calc(1lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"2\"])){min-block-size:calc(2lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"3\"])){min-block-size:calc(3lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"4\"])){min-block-size:calc(4lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"5\"])){min-block-size:calc(5lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"6\"])){min-block-size:calc(6lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"7\"])){min-block-size:calc(7lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"8\"])){min-block-size:calc(8lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"9\"])){min-block-size:calc(9lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}:host(:where([skraAutoResize][rows=\"10\"])){min-block-size:calc(10lh + 2 * var(--skra-comp-field-padding-block) + 2 * var(--skra-comp-field-border-width))}@media(hover:hover)and (pointer:fine){:host(:hover:not(:disabled):not([readonly])){background:var(--skra-comp-field-background-hover);border-color:var(--skra-comp-field-border-color-hover)}}:host(:focus-visible){border-color:var(--skra-comp-field-focus-ring-color);box-shadow:0 0 0 var(--skra-comp-field-focus-ring-width) var(--skra-comp-field-focus-ring-color)}:host(:disabled){cursor:not-allowed;opacity:var(--skra-sys-opacity-disabled)}:host([readonly]){cursor:default}:host([aria-invalid=true]){border-color:var(--skra-comp-field-error-color)}:host([aria-invalid=true]:focus-visible){box-shadow:0 0 0 var(--skra-comp-field-focus-ring-width) var(--skra-comp-field-error-color)}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n"] }]
71
75
  }], ctorParameters: () => [] });
72
76
 
73
77
  /**
@@ -86,6 +90,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImpor
86
90
  * Wide selector (textarea + input) reflects that `field-sizing: content` is a
87
91
  * native CSS capability for both: textarea grows in block-size with rows,
88
92
  * input grows in inline-size with content width.
93
+ *
94
+ * On a textarea the marker only ADDS growth (GitHub #12):
95
+ * - `rows` stays the minimum. Engines ignore `rows` under `field-sizing`, so
96
+ * `textarea[skraTextarea]` restores the floor from its own field box
97
+ * (`rows` 1–10; none = the native 2). A bare textarea gets no floor.
98
+ * - No manual resize handle where `field-sizing` ships — a drag would pin an
99
+ * inline height and end the growth.
100
+ * - No maximum. How tall the field may grow is the consumer's layout: a
101
+ * `max-block-size` caps it, then it scrolls.
89
102
  */
90
103
  class SkraAutoResizeDirective {
91
104
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraAutoResizeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -1 +1 @@
1
- {"version":3,"file":"skra-ui-textarea.mjs","sources":["../../../../libs/ui/textarea/src/lib/textarea/textarea.ts","../../../../libs/ui/textarea/src/lib/auto-resize/auto-resize.directive.ts","../../../../libs/ui/textarea/src/skra-ui-textarea.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n ViewEncapsulation,\n effect,\n inject,\n} from '@angular/core';\nimport { SkraFieldContext } from '@skra/ui/field';\n\n/**\n * `textarea[skraTextarea]` — multiline text atom. Per ADR 040 this is a\n * Component-on-a-native-host carrying its own host-scoped CSS (`textarea.scss`)\n * via Emulated encapsulation, replacing the ADR 038 slice branch.\n *\n * `<textarea>` is a content-bearing host (its value is its text content), so the\n * template MUST be `<ng-content>` — `template: ''` would discard the consumer's\n * initial text. See ADR 040 §2.\n *\n * Sibling atom to SkraInput — split per atom-granularity rule (memory\n * `feedback_atom_granularity.md`): textarea has capabilities `<input>` does not\n * (multi-line content, native `rows` / `cols`, autosize composition via the\n * sibling `[skraAutoResize]` directive).\n *\n * <skra-field>\n * <label skraLabelFor>Bio</label>\n * <textarea skraTextarea skraAutoResize rows=\"3\"></textarea>\n * </skra-field>\n *\n * <!-- standalone: native implicit label (wrapping) — no id/for needed -->\n * <label>Notes <textarea skraTextarea></textarea></label>\n *\n * Native `rows` / `cols` remain the consumer-facing sizing primitives — Forge\n * does not duplicate them as Angular inputs (boundary #9 native-attr-first).\n *\n * `SkraFieldContext` is injected `{ optional: true }` (ADR 013 §3.7) — same\n * posture as SkraInput / SkraCheckbox. Standalone, the atom renders the skin\n * only and the consumer owns every attribute, including the accessible name.\n */\n@Component({\n selector: 'textarea[skraTextarea]',\n standalone: true,\n // Content-bearing host — see ADR 040 §2.\n template: '<ng-content></ng-content>',\n styleUrl: './textarea.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n // ARIA relationships — Skra-owned, present only while a field cascades them.\n '[attr.aria-describedby]': 'context?.describedByIds() ?? null',\n '[attr.aria-invalid]': 'context?.hasError() ? \"true\" : null',\n '[attr.aria-required]': 'context?.isRequired() ? \"true\" : null',\n // Availability — native attrs only (native-attr-first, boundary #9).\n // <textarea> supports native `readonly`/`disabled`; NO `aria-readonly`/\n // `aria-disabled` duplicate. Readonly styling keys off `:read-only`.\n // Driven imperatively below, NOT as host bindings — see the constructor\n // note on standalone attribute ownership.\n },\n})\nexport class SkraTextarea {\n private readonly el = inject<ElementRef<HTMLTextAreaElement>>(ElementRef);\n readonly context = inject(SkraFieldContext, { optional: true });\n\n constructor() {\n const el = this.el.nativeElement;\n\n // Field-cascaded native properties — only bind when context is present so\n // standalone consumers retain full control of their static attributes.\n // Mirrors SkraInput / SkraCheckbox.\n if (this.context) {\n const ctx = this.context;\n effect(() => {\n el.id = ctx.fieldId();\n });\n effect(() => {\n el.disabled = ctx.isDisabled();\n });\n effect(() => {\n el.readOnly = ctx.isReadonly();\n });\n }\n }\n}\n","import { Directive } from '@angular/core';\n\n/**\n * Marker directive enabling content-driven sizing on a native form control via\n * the modern CSS `field-sizing: content` declaration shipped from the skin's\n * global utilities layer (`tokens/skins/skra/utilities.scss`).\n *\n * <textarea skraTextarea skraAutoResize rows=\"2\"></textarea>\n * <input skraInput skraAutoResize type=\"text\" />\n *\n * Composition-over-configuration: the host opts in by adding the attribute,\n * not by toggling a boolean Angular input. The directive has no host bindings,\n * no DI, and no template — its sole job is to make the selector available at\n * Angular's compilation stage; CSS does the rest.\n *\n * Wide selector (textarea + input) reflects that `field-sizing: content` is a\n * native CSS capability for both: textarea grows in block-size with rows,\n * input grows in inline-size with content width.\n */\n@Directive({\n selector: 'textarea[skraAutoResize], input[skraAutoResize]',\n standalone: true,\n})\nexport class SkraAutoResizeDirective {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BG;MAqBU,YAAY,CAAA;AACN,IAAA,EAAE,GAAG,MAAM,CAAkC,UAAU,CAAC;IAChE,OAAO,GAAG,MAAM,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAE/D,IAAA,WAAA,GAAA;AACE,QAAA,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,aAAa;;;;AAKhC,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE;AAChB,YAAA,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO;YACxB,MAAM,CAAC,MAAK;AACV,gBAAA,EAAE,CAAC,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE;AACvB,YAAA,CAAC,CAAC;YACF,MAAM,CAAC,MAAK;AACV,gBAAA,EAAE,CAAC,QAAQ,GAAG,GAAG,CAAC,UAAU,EAAE;AAChC,YAAA,CAAC,CAAC;YACF,MAAM,CAAC,MAAK;AACV,gBAAA,EAAE,CAAC,QAAQ,GAAG,GAAG,CAAC,UAAU,EAAE;AAChC,YAAA,CAAC,CAAC;QACJ;IACF;uGAtBW,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAZ,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,YAAY,ySAhBb,2BAA2B,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,whDAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAgB1B,YAAY,EAAA,UAAA,EAAA,CAAA;kBApBxB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,wBAAwB,EAAA,UAAA,EACtB,IAAI,EAAA,QAAA,EAEN,2BAA2B,EAAA,eAAA,EAEpB,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,IAAA,EACnC;;AAEJ,wBAAA,yBAAyB,EAAE,mCAAmC;AAC9D,wBAAA,qBAAqB,EAAE,qCAAqC;AAC5D,wBAAA,sBAAsB,EAAE,uCAAuC;;;;;;AAMhE,qBAAA,EAAA,MAAA,EAAA,CAAA,whDAAA,CAAA,EAAA;;;ACvDH;;;;;;;;;;;;;;;;AAgBG;MAKU,uBAAuB,CAAA;uGAAvB,uBAAuB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAvB,uBAAuB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,iDAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAvB,uBAAuB,EAAA,UAAA,EAAA,CAAA;kBAJnC,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,iDAAiD;AAC3D,oBAAA,UAAU,EAAE,IAAI;AACjB,iBAAA;;;ACtBD;;AAEG;;;;"}
1
+ {"version":3,"file":"skra-ui-textarea.mjs","sources":["../../../../libs/ui/textarea/src/lib/textarea/textarea.ts","../../../../libs/ui/textarea/src/lib/auto-resize/auto-resize.directive.ts","../../../../libs/ui/textarea/src/skra-ui-textarea.ts"],"sourcesContent":["import {\r\n ChangeDetectionStrategy,\r\n Component,\r\n ElementRef,\r\n ViewEncapsulation,\r\n effect,\r\n inject,\r\n} from '@angular/core';\r\nimport { SkraFieldContext } from '@skra/ui/field';\r\n\r\n/**\r\n * `textarea[skraTextarea]` — multiline text atom. Per ADR 040 this is a\r\n * Component-on-a-native-host carrying its own host-scoped CSS (`textarea.scss`)\r\n * via Emulated encapsulation, replacing the ADR 038 slice branch.\r\n *\r\n * `<textarea>` is a content-bearing host (its value is its text content), so the\r\n * template MUST be `<ng-content>` — `template: ''` would discard the consumer's\r\n * initial text. See ADR 040 §2.\r\n *\r\n * Sibling atom to SkraInput — split per atom-granularity rule (memory\r\n * `feedback_atom_granularity.md`): textarea has capabilities `<input>` does not\r\n * (multi-line content, native `rows` / `cols`, autosize composition via the\r\n * sibling `[skraAutoResize]` directive).\r\n *\r\n * <skra-field>\r\n * <label skraLabelFor>Bio</label>\r\n * <textarea skraTextarea skraAutoResize rows=\"3\"></textarea>\r\n * </skra-field>\r\n *\r\n * <!-- standalone: native implicit label (wrapping) — no id/for needed -->\r\n * <label>Notes <textarea skraTextarea></textarea></label>\r\n *\r\n * Native `rows` / `cols` remain the consumer-facing sizing primitives — Forge\r\n * does not duplicate them as Angular inputs (boundary #9 native-attr-first).\r\n * The resize handle works in the block axis only (`resize: block`, GitHub #12);\r\n * a consumer wanting both axes declares `resize: both` in its own styles. With\r\n * `[skraAutoResize]` the handle is off and `rows` stays the minimum height; the\r\n * maximum is the consumer's `max-block-size`.\r\n *\r\n * `SkraFieldContext` is injected `{ optional: true }` (ADR 013 §3.7) — same\r\n * posture as SkraInput / SkraCheckbox. Standalone, the atom renders the skin\r\n * only and the consumer owns every attribute, including the accessible name.\r\n */\r\n@Component({\r\n selector: 'textarea[skraTextarea]',\r\n standalone: true,\r\n // Content-bearing host — see ADR 040 §2.\r\n template: '<ng-content></ng-content>',\r\n styleUrl: './textarea.scss',\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n encapsulation: ViewEncapsulation.Emulated,\r\n host: {\r\n // ARIA relationships — Skra-owned, present only while a field cascades them.\r\n '[attr.aria-describedby]': 'context?.describedByIds() ?? null',\r\n '[attr.aria-invalid]': 'context?.hasError() ? \"true\" : null',\r\n '[attr.aria-required]': 'context?.isRequired() ? \"true\" : null',\r\n // Availability — native attrs only (native-attr-first, boundary #9).\r\n // <textarea> supports native `readonly`/`disabled`; NO `aria-readonly`/\r\n // `aria-disabled` duplicate. Readonly styling keys off `:read-only`.\r\n // Driven imperatively below, NOT as host bindings — see the constructor\r\n // note on standalone attribute ownership.\r\n },\r\n})\r\nexport class SkraTextarea {\r\n private readonly el = inject<ElementRef<HTMLTextAreaElement>>(ElementRef);\r\n readonly context = inject(SkraFieldContext, { optional: true });\r\n\r\n constructor() {\r\n const el = this.el.nativeElement;\r\n\r\n // Field-cascaded native properties — only bind when context is present so\r\n // standalone consumers retain full control of their static attributes.\r\n // Mirrors SkraInput / SkraCheckbox.\r\n if (this.context) {\r\n const ctx = this.context;\r\n effect(() => {\r\n el.id = ctx.fieldId();\r\n });\r\n effect(() => {\r\n el.disabled = ctx.isDisabled();\r\n });\r\n effect(() => {\r\n el.readOnly = ctx.isReadonly();\r\n });\r\n }\r\n }\r\n}\r\n","import { Directive } from '@angular/core';\r\n\r\n/**\r\n * Marker directive enabling content-driven sizing on a native form control via\r\n * the modern CSS `field-sizing: content` declaration shipped from the skin's\r\n * global utilities layer (`tokens/skins/skra/utilities.scss`).\r\n *\r\n * <textarea skraTextarea skraAutoResize rows=\"2\"></textarea>\r\n * <input skraInput skraAutoResize type=\"text\" />\r\n *\r\n * Composition-over-configuration: the host opts in by adding the attribute,\r\n * not by toggling a boolean Angular input. The directive has no host bindings,\r\n * no DI, and no template — its sole job is to make the selector available at\r\n * Angular's compilation stage; CSS does the rest.\r\n *\r\n * Wide selector (textarea + input) reflects that `field-sizing: content` is a\r\n * native CSS capability for both: textarea grows in block-size with rows,\r\n * input grows in inline-size with content width.\r\n *\r\n * On a textarea the marker only ADDS growth (GitHub #12):\r\n * - `rows` stays the minimum. Engines ignore `rows` under `field-sizing`, so\r\n * `textarea[skraTextarea]` restores the floor from its own field box\r\n * (`rows` 1–10; none = the native 2). A bare textarea gets no floor.\r\n * - No manual resize handle where `field-sizing` ships — a drag would pin an\r\n * inline height and end the growth.\r\n * - No maximum. How tall the field may grow is the consumer's layout: a\r\n * `max-block-size` caps it, then it scrolls.\r\n */\r\n@Directive({\r\n selector: 'textarea[skraAutoResize], input[skraAutoResize]',\r\n standalone: true,\r\n})\r\nexport class SkraAutoResizeDirective {}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCG;MAqBU,YAAY,CAAA;AACN,IAAA,EAAE,GAAG,MAAM,CAAkC,UAAU,CAAC;IAChE,OAAO,GAAG,MAAM,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAE/D,IAAA,WAAA,GAAA;AACE,QAAA,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,aAAa;;;;AAKhC,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE;AAChB,YAAA,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO;YACxB,MAAM,CAAC,MAAK;AACV,gBAAA,EAAE,CAAC,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE;AACvB,YAAA,CAAC,CAAC;YACF,MAAM,CAAC,MAAK;AACV,gBAAA,EAAE,CAAC,QAAQ,GAAG,GAAG,CAAC,UAAU,EAAE;AAChC,YAAA,CAAC,CAAC;YACF,MAAM,CAAC,MAAK;AACV,gBAAA,EAAE,CAAC,QAAQ,GAAG,GAAG,CAAC,UAAU,EAAE;AAChC,YAAA,CAAC,CAAC;QACJ;IACF;uGAtBW,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAZ,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,YAAY,ySAhBb,2BAA2B,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,+tGAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAgB1B,YAAY,EAAA,UAAA,EAAA,CAAA;kBApBxB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,wBAAwB,EAAA,UAAA,EACtB,IAAI,EAAA,QAAA,EAEN,2BAA2B,EAAA,eAAA,EAEpB,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,IAAA,EACnC;;AAEJ,wBAAA,yBAAyB,EAAE,mCAAmC;AAC9D,wBAAA,qBAAqB,EAAE,qCAAqC;AAC5D,wBAAA,sBAAsB,EAAE,uCAAuC;;;;;;AAMhE,qBAAA,EAAA,MAAA,EAAA,CAAA,+tGAAA,CAAA,EAAA;;;AC3DH;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;MAKU,uBAAuB,CAAA;uGAAvB,uBAAuB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAvB,uBAAuB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,iDAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAvB,uBAAuB,EAAA,UAAA,EAAA,CAAA;kBAJnC,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,iDAAiD;AAC3D,oBAAA,UAAU,EAAE,IAAI;AACjB,iBAAA;;;AC/BD;;AAEG;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skra/ui",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "license": "Apache-2.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -10,7 +10,7 @@
10
10
  "@angular/core": ">=22.0.0 <23.0.0",
11
11
  "@angular/common": ">=22.0.0 <23.0.0",
12
12
  "@angular/cdk": ">=22.0.0 <23.0.0",
13
- "@skra/tokens": "^0.9.0"
13
+ "@skra/tokens": "^0.9.1"
14
14
  },
15
15
  "dependencies": {
16
16
  "@floating-ui/dom": "^1.7.6",
@@ -25,6 +25,10 @@ import * as i0 from '@angular/core';
25
25
  *
26
26
  * Native `rows` / `cols` remain the consumer-facing sizing primitives — Forge
27
27
  * does not duplicate them as Angular inputs (boundary #9 native-attr-first).
28
+ * The resize handle works in the block axis only (`resize: block`, GitHub #12);
29
+ * a consumer wanting both axes declares `resize: both` in its own styles. With
30
+ * `[skraAutoResize]` the handle is off and `rows` stays the minimum height; the
31
+ * maximum is the consumer's `max-block-size`.
28
32
  *
29
33
  * `SkraFieldContext` is injected `{ optional: true }` (ADR 013 §3.7) — same
30
34
  * posture as SkraInput / SkraCheckbox. Standalone, the atom renders the skin
@@ -54,6 +58,15 @@ declare class SkraTextarea {
54
58
  * Wide selector (textarea + input) reflects that `field-sizing: content` is a
55
59
  * native CSS capability for both: textarea grows in block-size with rows,
56
60
  * input grows in inline-size with content width.
61
+ *
62
+ * On a textarea the marker only ADDS growth (GitHub #12):
63
+ * - `rows` stays the minimum. Engines ignore `rows` under `field-sizing`, so
64
+ * `textarea[skraTextarea]` restores the floor from its own field box
65
+ * (`rows` 1–10; none = the native 2). A bare textarea gets no floor.
66
+ * - No manual resize handle where `field-sizing` ships — a drag would pin an
67
+ * inline height and end the growth.
68
+ * - No maximum. How tall the field may grow is the consumer's layout: a
69
+ * `max-block-size` caps it, then it scrolls.
57
70
  */
58
71
  declare class SkraAutoResizeDirective {
59
72
  static ɵfac: i0.ɵɵFactoryDeclaration<SkraAutoResizeDirective, never>;
@@ -1 +1 @@
1
- {"version":3,"file":"skra-ui-textarea.d.ts","sources":["../../../../libs/ui/textarea/src/lib/textarea/textarea.ts","../../../../libs/ui/textarea/src/lib/auto-resize/auto-resize.directive.ts"],"mappings":";;;AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BG;AACH,cAoBa,YAAY;AACvB;sBACgB,gBAAA;;yCAFL,YAAY;2CAAZ,YAAY;AAuBxB;;AChFD;;;;;;;;;;;;;;;;AAgBG;AACH,cAIa,uBAAuB;yCAAvB,uBAAuB;2CAAvB,uBAAuB;AAAG;;;;","names":[]}
1
+ {"version":3,"file":"skra-ui-textarea.d.ts","sources":["../../../../libs/ui/textarea/src/lib/textarea/textarea.ts","../../../../libs/ui/textarea/src/lib/auto-resize/auto-resize.directive.ts"],"mappings":";;;AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCG;AACH,cAoBa,YAAY;AACvB;sBACgB,gBAAA;;yCAFL,YAAY;2CAAZ,YAAY;AAuBxB;;ACpFD;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;AACH,cAIa,uBAAuB;yCAAvB,uBAAuB;2CAAvB,uBAAuB;AAAG;;;;","names":[]}