mn-angular-lib 1.0.167 → 1.0.168

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.
@@ -285,6 +285,9 @@ const mnButtonVariants = tv({
285
285
  two_xl: 'rounded-2xl',
286
286
  three_xl: 'rounded-3xl',
287
287
  four_xl: 'rounded-4xl',
288
+ // A pill. Also what a fully-rounded container (e.g. a segmented track)
289
+ // hands its segments, since there is no tighter step below it.
290
+ full: 'rounded-full',
288
291
  },
289
292
  // Icon-button shape. `circle`/`square` drop the text padding and make the button a
290
293
  // fixed square sized off `size` (see compoundVariants), overriding `borderRadius`.
@@ -13868,37 +13871,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
13868
13871
  * row of loose buttons: the segments share one bordered surface, so they read as
13869
13872
  * one control with one answer instead of several independent actions. Theme
13870
13873
  * tokens only, so it holds up in both light and dark.
13874
+ *
13875
+ * Each segment is an `mnButton`, which owns its own size, colour, radius and
13876
+ * disabled look; the `segment` slot adds only what the button does not know
13877
+ * about — how it sits inside the track.
13871
13878
  */
13872
13879
  const mnSegmentedVariants = tv({
13873
13880
  slots: {
13874
13881
  root: 'inline-flex items-center gap-0.5 border border-base-300 bg-base-200 p-0.5',
13875
- segment: 'inline-flex items-center justify-center gap-1.5 cursor-pointer select-none ' +
13876
- 'whitespace-nowrap border border-transparent transition-colors ' +
13877
- 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary ' +
13878
- 'disabled:opacity-50 disabled:pointer-events-none',
13882
+ segment: 'gap-1.5 select-none ' +
13883
+ 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary',
13879
13884
  },
13880
13885
  variants: {
13881
13886
  /**
13882
- * Corner rounding of the track, with the segments rounded one step tighter so
13883
- * a filled segment nests inside the track's own corner instead of poking out
13884
- * of it. Token names match mn-button's; `full` makes a pill, where track and
13885
- * segment share the same fully-rounded ends.
13887
+ * Corner rounding of the track. The segments are rounded one step tighter
13888
+ * (see `SEGMENT_RADIUS` on the component) so a filled segment nests inside
13889
+ * the track's own corner instead of poking out of it. Token names match
13890
+ * mn-button's; `full` makes a pill, where track and segment share the same
13891
+ * fully-rounded ends.
13886
13892
  */
13887
13893
  borderRadius: {
13888
- none: { root: 'rounded-none', segment: 'rounded-none' },
13889
- xs: { root: 'rounded-xs', segment: 'rounded-none' },
13890
- sm: { root: 'rounded-sm', segment: 'rounded-xs' },
13891
- md: { root: 'rounded-md', segment: 'rounded-sm' },
13892
- lg: { root: 'rounded-lg', segment: 'rounded-md' },
13893
- xl: { root: 'rounded-xl', segment: 'rounded-lg' },
13894
- two_xl: { root: 'rounded-2xl', segment: 'rounded-xl' },
13895
- three_xl: { root: 'rounded-3xl', segment: 'rounded-2xl' },
13896
- four_xl: { root: 'rounded-4xl', segment: 'rounded-3xl' },
13897
- full: { root: 'rounded-full', segment: 'rounded-full' },
13898
- },
13899
- size: {
13900
- sm: { segment: 'px-2.5 py-1 text-sm' },
13901
- md: { segment: 'px-3 py-1.5 text-base' },
13894
+ none: { root: 'rounded-none' },
13895
+ xs: { root: 'rounded-xs' },
13896
+ sm: { root: 'rounded-sm' },
13897
+ md: { root: 'rounded-md' },
13898
+ lg: { root: 'rounded-lg' },
13899
+ xl: { root: 'rounded-xl' },
13900
+ two_xl: { root: 'rounded-2xl' },
13901
+ three_xl: { root: 'rounded-3xl' },
13902
+ four_xl: { root: 'rounded-4xl' },
13903
+ full: { root: 'rounded-full' },
13902
13904
  },
13903
13905
  /**
13904
13906
  * Stretch the track and share its width evenly between the segments.
@@ -13913,17 +13915,17 @@ const mnSegmentedVariants = tv({
13913
13915
  false: {},
13914
13916
  },
13915
13917
  /**
13916
- * The picked segment. Filled rather than merely tinted: the control is often
13917
- * the only thing on its row, so the answer has to be readable at a glance
13918
- * without comparing two subtle shades.
13918
+ * Whether the segment is the picked one. The colours come from the button
13919
+ * itself (a filled primary button when active, a ghost one otherwise); this
13920
+ * only keeps an inactive segment's box the same size as an active one, whose
13921
+ * fill variant carries a real border.
13919
13922
  */
13920
13923
  active: {
13921
- true: { segment: 'bg-primary text-primary-content' },
13922
- false: { segment: 'bg-transparent text-base-content hover:bg-base-content/10' },
13924
+ true: {},
13925
+ false: { segment: 'border border-transparent' },
13923
13926
  },
13924
13927
  },
13925
13928
  defaultVariants: {
13926
- size: 'md',
13927
13929
  borderRadius: 'lg',
13928
13930
  justified: false,
13929
13931
  active: false,
@@ -13932,6 +13934,23 @@ const mnSegmentedVariants = tv({
13932
13934
 
13933
13935
  /** Icon edge length per size, so a segment's glyph matches its own text. */
13934
13936
  const ICON_SIZE = { sm: 16, md: 18 };
13937
+ /**
13938
+ * Segment radius per track radius: one step tighter, so a filled segment nests
13939
+ * inside the track's corner instead of poking out of it. A pill has no tighter
13940
+ * step, so its segments are pills too.
13941
+ */
13942
+ const SEGMENT_RADIUS = {
13943
+ none: 'none',
13944
+ xs: 'none',
13945
+ sm: 'xs',
13946
+ md: 'sm',
13947
+ lg: 'md',
13948
+ xl: 'lg',
13949
+ two_xl: 'xl',
13950
+ three_xl: 'two_xl',
13951
+ four_xl: 'three_xl',
13952
+ full: 'full',
13953
+ };
13935
13954
  /**
13936
13955
  * A segmented control: two or three mutually exclusive choices sharing one
13937
13956
  * track, of which exactly one is active — a view switch (list ⇄ calendar), a
@@ -13944,6 +13963,10 @@ const ICON_SIZE = { sm: 16, md: 18 };
13944
13963
  * URL, so it can sit on a page that already has a tab bar without the two
13945
13964
  * fighting over the query string.
13946
13965
  *
13966
+ * Each segment is an `mnButton` — a filled primary one when active, a ghost one
13967
+ * otherwise — so the control inherits the button's sizes, colours and disabled
13968
+ * look rather than maintaining a parallel set.
13969
+ *
13947
13970
  * For switching between panes of a page, reach for `mn-tab`; this is for
13948
13971
  * switching how one pane is rendered.
13949
13972
  */
@@ -13966,10 +13989,9 @@ class MnSegmented {
13966
13989
  valueChange = new EventEmitter();
13967
13990
  /** Resolves this control's own accessible names against the app's bundle. */
13968
13991
  lang = inject(MnLanguageService);
13969
- /** Resolved slot classes for the current size and layout. */
13992
+ /** Resolved slot classes for the current rounding and layout. */
13970
13993
  get styles() {
13971
13994
  return mnSegmentedVariants({
13972
- size: this.dataSource.size,
13973
13995
  borderRadius: this.dataSource.borderRadius,
13974
13996
  justified: this.justified,
13975
13997
  });
@@ -13991,17 +14013,35 @@ class MnSegmented {
13991
14013
  return item.value === this.value;
13992
14014
  }
13993
14015
  /**
13994
- * Classes for one segment, which differ only in whether it is the active one.
14016
+ * Layout classes for one segment how it sits in the track. Its size, colour,
14017
+ * radius and disabled look come from the button itself; see
14018
+ * {@link segmentButton}.
13995
14019
  * @param item - The segment to style.
13996
14020
  */
13997
14021
  segmentClass(item) {
13998
14022
  return mnSegmentedVariants({
13999
- size: this.dataSource.size,
14000
- borderRadius: this.dataSource.borderRadius,
14001
14023
  justified: this.justified,
14002
14024
  active: this.isActive(item),
14003
14025
  }).segment();
14004
14026
  }
14027
+ /**
14028
+ * The `mnButton` configuration of one segment: a filled primary button when it
14029
+ * is the active choice, a ghost button otherwise, at the control's size and one
14030
+ * radius step inside the track. The active segment has no hover — re-picking it
14031
+ * does nothing, so nothing should invite the click.
14032
+ * @param item - The segment to configure.
14033
+ */
14034
+ segmentButton(item) {
14035
+ const active = this.isActive(item);
14036
+ return {
14037
+ size: this.dataSource.size ?? 'md',
14038
+ variant: active ? 'fill' : 'ghost',
14039
+ color: active ? 'primary' : 'gray',
14040
+ hover: !active,
14041
+ borderRadius: SEGMENT_RADIUS[this.dataSource.borderRadius ?? 'lg'],
14042
+ disabled: item.disabled ?? false,
14043
+ };
14044
+ }
14005
14045
  /**
14006
14046
  * Accessible name for an icon-only segment, or null when the segment shows a
14007
14047
  * label — that label already names it, and a second name would only compete.
@@ -14031,11 +14071,11 @@ class MnSegmented {
14031
14071
  this.valueChange.emit(item.value);
14032
14072
  }
14033
14073
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnSegmented, deps: [], target: i0.ɵɵFactoryTarget.Component });
14034
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnSegmented, isStandalone: true, selector: "mn-segmented", inputs: { dataSource: "dataSource", value: "value", justified: "justified" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<!-- 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 \u2014 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 <button\n (click)=\"select(item)\"\n [attr.aria-label]=\"segmentLabel(item)\"\n [attr.aria-pressed]=\"isActive(item)\"\n [class]=\"segmentClass(item)\"\n [disabled]=\"item.disabled ?? false\"\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 \u2014 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", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }, { kind: "pipe", type: MnTranslatePipe, name: "mnTranslate" }] });
14074
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: MnSegmented, isStandalone: true, selector: "mn-segmented", inputs: { dataSource: "dataSource", value: "value", justified: "justified" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<!-- 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 \u2014 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 \u2014 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", dependencies: [{ kind: "component", type: MnButton, selector: "button[mnButton], a[mnButton]", inputs: ["data"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }, { kind: "pipe", type: MnTranslatePipe, name: "mnTranslate" }] });
14035
14075
  }
14036
14076
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MnSegmented, decorators: [{
14037
14077
  type: Component,
14038
- args: [{ selector: 'mn-segmented', standalone: true, imports: [MnTranslatePipe, NgTemplateOutlet, LucideDynamicIcon], template: "<!-- 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 \u2014 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 <button\n (click)=\"select(item)\"\n [attr.aria-label]=\"segmentLabel(item)\"\n [attr.aria-pressed]=\"isActive(item)\"\n [class]=\"segmentClass(item)\"\n [disabled]=\"item.disabled ?? false\"\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 \u2014 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" }]
14078
+ args: [{ selector: 'mn-segmented', standalone: true, imports: [MnButton, MnTranslatePipe, NgTemplateOutlet, LucideDynamicIcon], template: "<!-- 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 \u2014 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 \u2014 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" }]
14039
14079
  }], propDecorators: { dataSource: [{
14040
14080
  type: Input,
14041
14081
  args: [{ required: true }]