/*
Field

Builds on @elements/style's .field (a label, a control, .hint and .error)
and keeps its meaning: one labelled form field. Adds orientation, a
message line that the description and the error share (so an error never
moves anything), validation state as data attributes, and the parts for
grouping fields (.field-set, .field-legend, .field-group,
.field-separator) and for choice cards (a .field-label wrapping a .field).

  .field[data-orientation]   vertical | horizontal | responsive
  .field[data-invalid]       label and message turn danger
  .field-message             one reserved line: description, or the error
  .field-error-auto          the field's own message
  .field-error-part          a <FieldError>, shown when [data-visible]
  .field-content             label and description beside a control
  .field-group               a column of fields; a container for responsive
  .field-set / .field-legend a native fieldset without the box
  .field-separator           a rule with optional text
*/

@layer components {

.field {
  display:        flex;
  flex-direction: column;
  gap:            var(--space-2);
  width:          100%;
  min-width:      0;
}

/* @elements/style gives .field > label and .field > .hint a margin; the
   field spaces with gap instead. */
.field > .field-label,
.field > .label,
.field > .field-message > .hint,
.field > .field-message > .error,
.field .field-content > .field-label,
.field .field-content > .field-message > .hint,
.field .field-content > .field-message > .error {
  margin: 0;
}

.field-label {
  width:         fit-content;
  max-width:     100%;
  min-width:     0;
  overflow-wrap: anywhere;
}

/* @elements/style sizes .field > label at --text-sm; the label role wins. */
.field > .field-label {
  display:     inline-flex;
  font-size:   var(--text-label);
  font-weight: var(--weight-ui);
}

.field-required {
  margin-inline-start: calc(var(--space-1) / 2);
  color:               var(--danger);
}

/* MESSAGE LINE: description and error stacked in one grid cell. The cell
   is as tall as the taller of the two, and is always there. */

.field-message {
  display:     grid;
  min-height:  calc(var(--leading-snug) * 1em);
  font-size:   var(--text-caption);
  line-height: var(--leading-snug);
}

.field-message[data-empty] {
  display: none;
}

.field-message > * {
  grid-area: 1 / 1;
  min-width: 0;
}

/* FieldErrors wrapped in one slot="error" element stack in one cell too. */
.field-message > :not(.field-description, .field-error-auto, .field-error-part) {
  display: grid;
}

.field-message > * > .field-error-part {
  grid-area: 1 / 1;
}

.field-description {
  margin:      0;
  color:       var(--ink-soft);
  font-family: var(--font-body);
  font-size:   var(--text-caption);
  line-height: var(--leading-snug);
}

.field-description a {
  color:                 inherit;
  text-decoration:       underline;
  text-underline-offset: auto;
}

.field-description a:hover {
  color: var(--ink);
}

.field-error-auto,
.field-error-part {
  margin:      0;
  color:       var(--danger);
  font-family: var(--font-body);
  font-size:   var(--text-caption);
  line-height: var(--leading-snug);
}

.field-error-auto:not([data-visible]),
.field-error-part:not([data-visible]) {
  visibility: hidden;
}

/* An error takes the description's place in the line. */
.field[data-error-shown] > .field-message > .field-description,
.field[data-error-shown] > .field-message > .field-error-auto:not([data-visible]),
.field[data-error-shown] > .field-content > .field-message > .field-description,
.field[data-error-shown] > .field-content > .field-message > .field-error-auto:not([data-visible]) {
  visibility: hidden;
}

/* One issue keeps to the reserved line too, shown or hidden: the field's own
   message (a long browser validationMessage at phone width) ends in an
   ellipsis rather than wrapping, so it never grows the field. The full text
   stays in the accessible description and the title. */
.field-error-text {
  display:       block;
  overflow:      hidden;
  white-space:   nowrap;
  text-overflow: ellipsis;
}

/* Several issues share the one reserved line, so the field keeps its height
   valid or invalid at any width: the items run inline and the line ends in
   an ellipsis when they do not fit. Every issue stays in the accessible
   description, and the list's title holds them all. */
.field-error-list {
  display:       block;
  margin:        0;
  padding:       0;
  list-style:    none;
  overflow:      hidden;
  white-space:   nowrap;
  text-overflow: ellipsis;
  font-size:     inherit;
  line-height:   var(--leading-snug);
}

.field-error-list > li {
  display:      inline;
  margin:       0;
  padding:      0;
  line-height:  inherit;
}

.field-error-list > li + li {
  margin-inline-start: var(--space-3);
}

/* A clipped message (data-clipped, set by the controller) opens its full
   text over what follows while the pointer is on it or focus is in the
   field, so a touch user who taps the control reads all of it. The open
   text is positioned, so the line and everything after it stay put. */
.field-error-auto {
  position: relative;
}

.field-error-auto[data-visible][data-clipped]:not([data-measuring]):hover > :is(.field-error-text, .field-error-list),
.field:focus-within .field-error-auto[data-visible][data-clipped]:not([data-measuring]) > :is(.field-error-text, .field-error-list) {
  position:          absolute;
  inset-block-start: calc(-1 * var(--space-1));
  inset-inline:      calc(-1 * var(--space-2));
  z-index:           var(--z-raised);
  padding:           var(--space-1) var(--space-2);
  border:            var(--surface-border-width) solid var(--surface-border);
  border-radius:     var(--radius-control);
  background:        var(--surface-bg);
  box-shadow:        var(--surface-shadow);
  white-space:       normal;
  overflow:          visible;
  overflow-wrap:     anywhere;
  text-overflow:     clip;
}

/* A <FieldError> placed in the default slot keeps its own line. */
.field > .field-error-part {
  min-height: calc(var(--leading-snug) * 1em);
}

/* States */

.field[data-invalid] > .field-label,
.field[data-invalid] > .field-content > .field-label {
  color: var(--danger);
}

/* Disabled is the effective state: the field's own prop or a native
   <fieldset disabled> around it, which disables its control as well. */
.field:is([data-disabled], fieldset:disabled .field) > :is(.field-label, .field-content, .field-message) {
  opacity: var(--opacity-disabled);
  cursor:  not-allowed;
}

/* Dims once: a choice card that already dims the whole card leaves the
   field inside it at full opacity. */
.field-label:has(> .field):has(:disabled) .field > :is(.field-label, .field-content, .field-message) {
  opacity: 1;
}

/* Orientation */

.field[data-orientation="horizontal"] {
  flex-direction: row;
  align-items:    center;
  gap:            var(--space-3);
}

.field[data-orientation="horizontal"] > .field-label {
  flex: 1 1 auto;
}

.field-content {
  display:        flex;
  flex:           1 1 auto;
  flex-direction: column;
  gap:            var(--space-1);
  min-width:      0;
  line-height:    var(--leading-snug);
}

.field-title {
  display:        flex;
  align-items:    center;
  gap:            var(--control-gap);
  width:          fit-content;
  color:          var(--ink);
  font-family:    var(--font-ui);
  font-size:      var(--text-label);
  font-weight:    var(--weight-ui);
  letter-spacing: var(--tracking-ui);
  line-height:    var(--leading-snug);
}

/* Groups */

.field-group {
  container:      field-group / inline-size;
  display:        flex;
  flex-direction: column;
  gap:            var(--space-5);
  width:          100%;
}

.field-group > .field-group {
  gap: var(--space-4);
}

.field[data-orientation="responsive"] {
  flex-direction: column;
}

/* Responsive puts the label and description first (above the control in a
   column, beside it in a row); horizontal puts the control first. */
.field[data-orientation="responsive"] > .field-content {
  order: -1;
}

@container field-group (min-width: 28rem) {
  .field[data-orientation="responsive"] {
    flex-direction: row;
    align-items:    flex-start;
    gap:            var(--space-4);
  }

  .field[data-orientation="responsive"] > .field-label,
  .field[data-orientation="responsive"] > .field-content {
    flex: 1 1 0;
  }

  .field[data-orientation="responsive"] > :not(.field-label, .field-content) {
    flex: 1 1 0;
  }
}

.field-set {
  display:        flex;
  flex-direction: column;
  gap:            var(--space-4);
  min-width:      0;
  margin:         0;
  padding:        0;
  border:         0;
}

/* A floated legend leaves the fieldset border and becomes a flex item, so
   the fieldset's gap spaces it like every other child. */
.field-legend {
  float:          left;
  width:          100%;
  margin:         0;
  padding:        0;
  color:          var(--ink);
  font-family:    var(--font-heading);
  font-size:      var(--text-body);
  font-weight:    var(--weight-strong);
  letter-spacing: var(--tracking-heading);
}

.field-legend[data-variant="label"] {
  font-family:    var(--font-ui);
  font-size:      var(--text-label);
  font-weight:    var(--weight-ui);
  letter-spacing: var(--tracking-ui);
}

.field-set > .field-legend + .field-description {
  margin-top: calc(var(--space-1) - var(--space-4));
}

.field-separator {
  position:    relative;
  display:     flex;
  align-items: center;
  min-height:  calc(var(--leading-snug) * 1em);
  color:       var(--ink-soft);
  font-family: var(--font-ui);
  font-size:   var(--text-caption);
}

.field-separator::before {
  content:    "";
  position:   absolute;
  inset:      50% 0 auto;
  border-top: var(--control-border-width) solid var(--rule);
}

.field-separator-content {
  position:   relative;
  margin:     0 auto;
  padding:    0 var(--space-2);
  background: var(--bg);
}

.field-separator-content:empty {
  display: none;
}

/* CHOICE CARD: a FieldLabel that wraps a Field. */

.field-label:has(> .field) {
  display:       flex;
  width:         100%;
  padding:       var(--surface-padding-sm);
  border:        var(--control-border-width) solid var(--control-border);
  border-radius: var(--radius-surface);
  background:    var(--surface-bg);
  cursor:        pointer;
  transition:    border-color var(--duration-fast) var(--ease-out),
                 background-color var(--duration-fast) var(--ease-out);
}

.field-label:has(> .field):hover {
  border-color: var(--rule-strong);
}

.field-label:has(> .field):has(:checked) {
  border-color: var(--accent);
  background:   var(--item-selected);
}

.field-label:has(> .field):has(:focus-visible) {
  box-shadow: var(--focus-ring);
}

.field-label:has(> .field):has(:disabled) {
  opacity: var(--opacity-disabled);
  cursor:  not-allowed;
}

}
