{"version":3,"file":"mn-angular-lib-segmented.mjs","sources":["../../../projects/mn-angular-lib/segmented/src/mn-segmented/mn-segmentedVariants.ts","../../../projects/mn-angular-lib/segmented/src/mn-segmented/mn-segmented.ts","../../../projects/mn-angular-lib/segmented/src/mn-segmented/mn-segmented.html","../../../projects/mn-angular-lib/segmented/public-api.ts","../../../projects/mn-angular-lib/segmented/mn-angular-lib-segmented.ts"],"sourcesContent":["import { tv, type VariantProps } from 'tailwind-variants';\n\n/**\n * Styling for {@link MnSegmented}, expressed as tailwind-variants slots so the\n * template pulls one class string per role.\n *\n * The control is a track with the active choice raised out of it, rather than a\n * row of loose buttons: the segments share one bordered surface, so they read as\n * one control with one answer instead of several independent actions. Theme\n * tokens only, so it holds up in both light and dark.\n *\n * Each segment is an `mnButton`, which owns its own size, colour, radius and\n * disabled look; the `segment` slot adds only what the button does not know\n * about — how it sits inside the track.\n */\nexport const mnSegmentedVariants = tv({\n  slots: {\n    root: 'inline-flex items-center gap-0.5 border border-base-300 bg-base-200 p-0.5',\n    segment:\n      'gap-1.5 select-none ' +\n      'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary',\n  },\n  variants: {\n    /**\n     * Corner rounding of the track. The segments are rounded one step tighter\n     * (see `SEGMENT_RADIUS` on the component) so a filled segment nests inside\n     * the track's own corner instead of poking out of it. Token names match\n     * mn-button's; `full` makes a pill, where track and segment share the same\n     * fully-rounded ends.\n     */\n    borderRadius: {\n      none: { root: 'rounded-none' },\n      xs: { root: 'rounded-xs' },\n      sm: { root: 'rounded-sm' },\n      md: { root: 'rounded-md' },\n      lg: { root: 'rounded-lg' },\n      xl: { root: 'rounded-xl' },\n      two_xl: { root: 'rounded-2xl' },\n      three_xl: { root: 'rounded-3xl' },\n      four_xl: { root: 'rounded-4xl' },\n      full: { root: 'rounded-full' },\n    },\n    /**\n     * Stretch the track and share its width evenly between the segments.\n     *\n     * The segments grow from a zero basis but keep their automatic minimum, so a\n     * justified control inside an auto-width parent still asks for the room its\n     * labels need. Letting them shrink below their content (`min-w-0`) made the\n     * parent resolve to a narrower box and truncated the labels instead.\n     */\n    justified: {\n      true: { root: 'flex w-full', segment: 'flex-1' },\n      false: {},\n    },\n    /**\n     * Whether the segment is the picked one. The colours come from the button\n     * itself (a filled primary button when active, a ghost one otherwise); this\n     * only keeps an inactive segment's box the same size as an active one, whose\n     * fill variant carries a real border.\n     */\n    active: {\n      true: {},\n      false: { segment: 'border border-transparent' },\n    },\n  },\n  defaultVariants: {\n    borderRadius: 'lg',\n    justified: false,\n    active: false,\n  },\n});\n\nexport type MnSegmentedVariants = VariantProps<typeof mnSegmentedVariants>;\n","import {\n  Component,\n  EventEmitter,\n  inject,\n  Input,\n  Output,\n  TemplateRef,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { LucideDynamicIcon } from '@lucide/angular';\nimport { MnLanguageService, MnTranslatePipe } from 'mn-angular-lib/core';\nimport { MnButton, MnButtonTypes } from 'mn-angular-lib/button';\nimport { MnSegmentedDataSource, MnSegmentedItem } from './mn-segmentedTypes';\nimport { MnSegmentedVariants, mnSegmentedVariants } from './mn-segmentedVariants';\n\n/** Icon edge length per size, so a segment's glyph matches its own text. */\nconst ICON_SIZE: Record<'sm' | 'md', number> = { sm: 16, md: 18 };\n\n/**\n * Segment radius per track radius: one step tighter, so a filled segment nests\n * inside the track's corner instead of poking out of it. A pill has no tighter\n * step, so its segments are pills too.\n */\nconst SEGMENT_RADIUS: Record<\n  NonNullable<MnSegmentedVariants['borderRadius']>,\n  NonNullable<MnButtonTypes['borderRadius']>\n> = {\n  none: 'none',\n  xs: 'none',\n  sm: 'xs',\n  md: 'sm',\n  lg: 'md',\n  xl: 'lg',\n  two_xl: 'xl',\n  three_xl: 'two_xl',\n  four_xl: 'three_xl',\n  full: 'full',\n};\n\n/**\n * A segmented control: two or three mutually exclusive choices sharing one\n * track, of which exactly one is active — a view switch (list ⇄ calendar), a\n * scope switch (mine ⇄ everyone), a range switch (week ⇄ month).\n *\n * The selection is **controlled**: the component renders whatever\n * {@link value} says and emits {@link valueChange} on a click, so the consumer's\n * own state stays the single source of truth for what is on screen. It keeps no\n * copy of the selection, and — unlike `mn-tab` — it does not mirror one into the\n * URL, so it can sit on a page that already has a tab bar without the two\n * fighting over the query string.\n *\n * Each segment is an `mnButton` — a filled primary one when active, a ghost one\n * otherwise — so the control inherits the button's sizes, colours and disabled\n * look rather than maintaining a parallel set.\n *\n * For switching between panes of a page, reach for `mn-tab`; this is for\n * switching how one pane is rendered.\n */\n@Component({\n  selector: 'mn-segmented',\n  standalone: true,\n  imports: [MnButton, MnTranslatePipe, NgTemplateOutlet, LucideDynamicIcon],\n  templateUrl: './mn-segmented.html',\n})\nexport class MnSegmented {\n  /** The choices and how the group is labelled and scaled. */\n  @Input({ required: true }) dataSource!: MnSegmentedDataSource;\n\n  /**\n   * Value of the active segment. No value (or one naming no segment) leaves the\n   * control with nothing active, which is what an unresolved selection should\n   * look like — the component never picks one on the consumer's behalf.\n   */\n  @Input() value?: string;\n\n  /**\n   * Whether the segments stretch to fill the available width. Defaults to false,\n   * so the control hugs its content and can be parked at the end of a row; turn\n   * it on where it should span its container, typically on a narrow screen.\n   */\n  @Input() justified = false;\n\n  /** Emits the picked segment's value. The consumer decides what to do with it. */\n  @Output() valueChange = new EventEmitter<string>();\n\n  /** Resolves this control's own accessible names against the app's bundle. */\n  private readonly lang = inject(MnLanguageService);\n\n  /** Resolved slot classes for the current rounding and layout. */\n  get styles() {\n    return mnSegmentedVariants({\n      borderRadius: this.dataSource.borderRadius,\n      justified: this.justified,\n    });\n  }\n\n  /** Icon edge length matching the control's text size. */\n  get iconSize(): number {\n    return ICON_SIZE[this.dataSource.size ?? 'md'];\n  }\n\n  /** Accessible name of the group, or null when the consumer named nothing. */\n  get groupLabel(): string | null {\n    const key = this.dataSource.ariaLabel;\n    return key ? this.lang.translate(key) : null;\n  }\n\n  /**\n   * Whether `item` is the active choice.\n   * @param item - The segment to test.\n   */\n  isActive(item: MnSegmentedItem): boolean {\n    return item.value === this.value;\n  }\n\n  /**\n   * Layout classes for one segment — how it sits in the track. Its size, colour,\n   * radius and disabled look come from the button itself; see\n   * {@link segmentButton}.\n   * @param item - The segment to style.\n   */\n  segmentClass(item: MnSegmentedItem): string {\n    return mnSegmentedVariants({\n      justified: this.justified,\n      active: this.isActive(item),\n    }).segment();\n  }\n\n  /**\n   * The `mnButton` configuration of one segment: a filled primary button when it\n   * is the active choice, a ghost button otherwise, at the control's size and one\n   * radius step inside the track. The active segment has no hover — re-picking it\n   * does nothing, so nothing should invite the click.\n   * @param item - The segment to configure.\n   */\n  segmentButton(item: MnSegmentedItem): Partial<MnButtonTypes> {\n    const active = this.isActive(item);\n    return {\n      size: this.dataSource.size ?? 'md',\n      variant: active ? 'fill' : 'ghost',\n      color: active ? 'primary' : 'gray',\n      hover: !active,\n      borderRadius: SEGMENT_RADIUS[this.dataSource.borderRadius ?? 'lg'],\n      disabled: item.disabled ?? false,\n    };\n  }\n\n  /**\n   * Accessible name for an icon-only segment, or null when the segment shows a\n   * label — that label already names it, and a second name would only compete.\n   * @param item - The segment to name.\n   */\n  segmentLabel(item: MnSegmentedItem): string | null {\n    if (item.label || !item.ariaLabel) return null;\n    return this.lang.translate(item.ariaLabel);\n  }\n\n  /**\n   * Whether an icon was supplied as a `TemplateRef` rather than lucide icon data.\n   * @param value - The icon to test.\n   */\n  isTemplateRef(value: unknown): value is TemplateRef<unknown> {\n    return value instanceof TemplateRef;\n  }\n\n  /**\n   * Announces a click. Re-picking the active segment is silent: it is not a\n   * change, and a consumer that reloads on every emission would refetch for\n   * nothing.\n   * @param item - The segment that was clicked.\n   */\n  select(item: MnSegmentedItem): void {\n    if (item.disabled || this.isActive(item)) return;\n    this.valueChange.emit(item.value);\n  }\n}\n","<!-- One control, one answer: `role=\"group\"` with `aria-pressed` per segment, so\n     the picked choice is announced as pressed rather than as a plain button\n     nobody can tell the state of. Not a radiogroup — these are buttons that act\n     immediately, not a value being edited inside a form. -->\n<div [attr.aria-label]=\"groupLabel\" [class]=\"styles.root()\" role=\"group\">\n  @for (item of dataSource.items; track item.value) {\n    <!-- Each segment is an mn-button: the active one a filled primary button, the\n         rest ghost buttons, so the control shares the button's sizes, colours and\n         disabled look instead of re-deriving them. -->\n    <button\n      mnButton\n      (click)=\"select(item)\"\n      [attr.aria-label]=\"segmentLabel(item)\"\n      [attr.aria-pressed]=\"isActive(item)\"\n      [class]=\"segmentClass(item)\"\n      [data]=\"segmentButton(item)\"\n      type=\"button\"\n    >\n      @if (item.icon; as icon) {\n        <span class=\"inline-flex shrink-0 items-center\">\n          <!-- A caller's template, else lucide data rendered at the size that\n               matches this control's text — the same icon convention the\n               dropdown's actions use. -->\n          @if (isTemplateRef(icon)) {\n            <ng-container [ngTemplateOutlet]=\"icon\"></ng-container>\n          } @else {\n            <svg [lucideIcon]=\"$any(icon)\" [size]=\"iconSize\"></svg>\n          }\n        </span>\n      }\n      @if (item.label; as label) {\n        <span class=\"truncate\">{{ label | mnTranslate }}</span>\n      }\n    </button>\n  }\n</div>\n","/**\n * Public API of the `mn-angular-lib/segmented` entry point: the segmented control.\n *\n * Each entry point is its own module in the published package, so a consumer's bundler\n * splits it into the chunk that uses it instead of loading the whole library at startup.\n * The root `mn-angular-lib` entry re-exports every entry point.\n */\nexport * from './src/mn-segmented';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}