@eunomia/elements 1.0.0-beta.4 → 1.0.0-beta.5

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.
Files changed (181) hide show
  1. package/dist/cdn/components/chunks/{chunk-SIHZFOTK.js → chunk-2XASWWKR.js} +1 -1
  2. package/dist/cdn/components/chunks/chunk-32DTNR52.js +1 -0
  3. package/dist/cdn/components/chunks/chunk-3MTUVMM5.js +1 -0
  4. package/dist/cdn/components/chunks/chunk-5GKM5DIB.js +1 -0
  5. package/dist/cdn/components/chunks/{chunk-GSJEJLFA.js → chunk-6ETDXUH2.js} +1 -1
  6. package/dist/cdn/components/chunks/{chunk-EJIX2D4Z.js → chunk-AIS3UICU.js} +7 -7
  7. package/dist/cdn/components/chunks/{chunk-BMQOI7HX.js → chunk-DKOPI7QA.js} +99 -55
  8. package/dist/cdn/components/chunks/{chunk-NHFWATVN.js → chunk-EAKYD33D.js} +3 -3
  9. package/dist/cdn/components/chunks/{chunk-EOWBYZV7.js → chunk-IDKZKLJR.js} +5 -5
  10. package/dist/cdn/components/chunks/chunk-JRGM3W7I.js +53 -0
  11. package/dist/cdn/components/chunks/{chunk-UAGCQF23.js → chunk-L65WUTXM.js} +5 -5
  12. package/dist/cdn/components/chunks/chunk-LFCXUVDO.js +1 -0
  13. package/dist/cdn/components/chunks/{chunk-YGUJJ25C.js → chunk-MPQP4R54.js} +2 -2
  14. package/dist/cdn/components/chunks/{chunk-RJCJXB7Y.js → chunk-NBIENHLV.js} +1 -1
  15. package/dist/cdn/components/chunks/{chunk-4MXL6PZC.js → chunk-OW7XKDCH.js} +13 -13
  16. package/dist/cdn/components/chunks/{chunk-IJ5WFMK4.js → chunk-SD2WHGPF.js} +7 -6
  17. package/dist/cdn/components/chunks/chunk-SJWRE6PW.js +1 -0
  18. package/dist/cdn/components/eun-animate.js +5 -3
  19. package/dist/cdn/components/eun-badge.js +7 -7
  20. package/dist/cdn/components/eun-breadcrumb-item.js +1 -1
  21. package/dist/cdn/components/eun-breadcrumb.js +1 -1
  22. package/dist/cdn/components/eun-button.js +1 -1
  23. package/dist/cdn/components/eun-calendar.js +13 -13
  24. package/dist/cdn/components/eun-card.js +42 -3
  25. package/dist/cdn/components/eun-carousel.js +2 -2
  26. package/dist/cdn/components/eun-check-card.js +1 -1
  27. package/dist/cdn/components/eun-checkbox-list.js +1 -1
  28. package/dist/cdn/components/eun-checkbox.js +1 -1
  29. package/dist/cdn/components/eun-chip.js +1 -1
  30. package/dist/cdn/components/eun-collapse.js +39 -20
  31. package/dist/cdn/components/eun-color-picker.js +3 -3
  32. package/dist/cdn/components/eun-counter.js +8 -8
  33. package/dist/cdn/components/eun-date-picker.js +19 -19
  34. package/dist/cdn/components/eun-drawer.js +9 -9
  35. package/dist/cdn/components/eun-dropdown-button.js +1 -1
  36. package/dist/cdn/components/eun-fab.js +6 -6
  37. package/dist/cdn/components/eun-icon.js +1 -1
  38. package/dist/cdn/components/eun-image-card.js +1 -1
  39. package/dist/cdn/components/eun-image-carousel.js +3 -3
  40. package/dist/cdn/components/eun-input-file.js +1 -1
  41. package/dist/cdn/components/eun-input-phone.js +18 -18
  42. package/dist/cdn/components/eun-input.js +1 -1
  43. package/dist/cdn/components/eun-link.js +116 -18
  44. package/dist/cdn/components/eun-modal.js +15 -15
  45. package/dist/cdn/components/eun-radio-card.js +1 -1
  46. package/dist/cdn/components/eun-radio-list.js +1 -1
  47. package/dist/cdn/components/eun-radio.js +1 -1
  48. package/dist/cdn/components/eun-scheduler-event-editor.js +7 -7
  49. package/dist/cdn/components/eun-scheduler-event.js +1 -1
  50. package/dist/cdn/components/eun-scheduler.js +18 -18
  51. package/dist/cdn/components/eun-search.js +15 -15
  52. package/dist/cdn/components/eun-select.js +2 -2
  53. package/dist/cdn/components/eun-slider.js +1 -1
  54. package/dist/cdn/components/eun-sliding-card.js +1 -1
  55. package/dist/cdn/components/eun-switch.js +1 -1
  56. package/dist/cdn/components/eun-tab-group.js +1 -1
  57. package/dist/cdn/components/eun-tab.js +1 -1
  58. package/dist/cdn/components/eun-table-action-bar.js +1 -1
  59. package/dist/cdn/components/eun-table.js +1 -1
  60. package/dist/cdn/components/eun-textarea.js +2 -2
  61. package/dist/cdn/components/eun-time-picker.js +11 -11
  62. package/dist/cdn/components/eun-toggle-group.js +30 -22
  63. package/dist/cdn/components/eun-toggle-item.js +10 -8
  64. package/dist/cdn/components/eun-toggle.js +1 -1
  65. package/dist/cdn/components/eun-toolbar.js +1 -1
  66. package/dist/components/action/button/button.d.ts +44 -17
  67. package/dist/components/action/button/button.js +100 -22
  68. package/dist/components/action/button/button.styles.js +77 -39
  69. package/dist/components/action/dropdown-button/dropdown-button.d.ts +8 -8
  70. package/dist/components/action/dropdown-button/dropdown-button.js +7 -7
  71. package/dist/components/action/fab/fab.d.ts +7 -6
  72. package/dist/components/action/fab/fab.js +11 -8
  73. package/dist/components/action/fab/fab.styles.js +4 -4
  74. package/dist/components/action/link/link.d.ts +65 -6
  75. package/dist/components/action/link/link.js +176 -20
  76. package/dist/components/action/link/link.styles.js +86 -1
  77. package/dist/components/action/shared-styles/button-palette.styles.d.ts +3 -3
  78. package/dist/components/action/shared-styles/button-palette.styles.js +14 -14
  79. package/dist/components/action/toggle/toggle-group.d.ts +15 -5
  80. package/dist/components/action/toggle/toggle-group.js +42 -17
  81. package/dist/components/action/toggle/toggle-group.styles.d.ts +2 -1
  82. package/dist/components/action/toggle/toggle-group.styles.js +15 -7
  83. package/dist/components/action/toggle/toggle-item.d.ts +4 -4
  84. package/dist/components/action/toggle/toggle-item.js +2 -2
  85. package/dist/components/action/toggle/toggle-item.styles.js +8 -6
  86. package/dist/components/alert/drawer/drawer.js +1 -1
  87. package/dist/components/alert/modal/modal.d.ts +8 -8
  88. package/dist/components/alert/modal/modal.js +11 -11
  89. package/dist/components/content/badge/badge.d.ts +6 -6
  90. package/dist/components/content/badge/badge.js +4 -4
  91. package/dist/components/content/badge/badge.styles.js +4 -4
  92. package/dist/components/content/card/card.d.ts +32 -4
  93. package/dist/components/content/card/card.js +64 -4
  94. package/dist/components/content/card/card.styles.js +33 -1
  95. package/dist/components/content/card/image-card.d.ts +2 -1
  96. package/dist/components/content/card/image-card.js +1 -0
  97. package/dist/components/content/card/sliding-card.d.ts +2 -1
  98. package/dist/components/content/card/sliding-card.js +1 -0
  99. package/dist/components/content/collapse/collapse.d.ts +29 -11
  100. package/dist/components/content/collapse/collapse.js +53 -23
  101. package/dist/components/content/collapse/collapse.styles.js +13 -0
  102. package/dist/components/content/icon/icon.d.ts +12 -2
  103. package/dist/components/content/icon/icon.js +52 -22
  104. package/dist/components/content/icon/icon.style.d.ts +2 -0
  105. package/dist/components/content/icon/icon.style.js +4 -1
  106. package/dist/components/content/shared-styles/card-link.styles.js +1 -0
  107. package/dist/components/form/color-picker/color-picker.d.ts +3 -3
  108. package/dist/components/form/color-picker/color-picker.js +4 -4
  109. package/dist/components/form/counter/counter.d.ts +3 -3
  110. package/dist/components/form/counter/counter.js +3 -3
  111. package/dist/components/form/counter/counter.styles.js +5 -5
  112. package/dist/components/form/date-picker/date-picker.d.ts +3 -3
  113. package/dist/components/form/date-picker/date-picker.js +3 -3
  114. package/dist/components/form/date-picker/date-picker.styles.js +5 -5
  115. package/dist/components/form/input/input.d.ts +3 -3
  116. package/dist/components/form/input/input.js +4 -4
  117. package/dist/components/form/input-phone/input-phone.d.ts +3 -3
  118. package/dist/components/form/input-phone/input-phone.js +3 -3
  119. package/dist/components/form/input-phone/input-phone.styles.js +12 -12
  120. package/dist/components/form/search/search.styles.js +3 -3
  121. package/dist/components/form/select/select.d.ts +3 -3
  122. package/dist/components/form/select/select.js +3 -3
  123. package/dist/components/form/shared-styles/field.styles.js +5 -5
  124. package/dist/components/form/textarea/textarea.d.ts +3 -3
  125. package/dist/components/form/textarea/textarea.js +4 -4
  126. package/dist/components/form/time-picker/time-picker.d.ts +3 -3
  127. package/dist/components/form/time-picker/time-picker.js +3 -3
  128. package/dist/components/form/time-picker/time-picker.styles.js +5 -5
  129. package/dist/components/layout/animate/animate.d.ts +8 -0
  130. package/dist/components/layout/animate/animate.js +20 -8
  131. package/dist/features/scheduler/calendar/calendar.d.ts +6 -6
  132. package/dist/features/scheduler/calendar/calendar.js +9 -9
  133. package/dist/features/scheduler/scheduler/scheduler.d.ts +6 -6
  134. package/dist/features/scheduler/scheduler/scheduler.js +13 -13
  135. package/dist/features/scheduler/scheduler-event/scheduler-event.d.ts +10 -8
  136. package/dist/features/scheduler/scheduler-event/scheduler-event.js +7 -7
  137. package/dist/features/scheduler/scheduler-event/scheduler-event.styles.js +6 -6
  138. package/dist/features/scheduler/scheduler-event-editor/scheduler-event-editor.js +5 -5
  139. package/dist/features/scheduler/shared/scheduler-event-popover.js +2 -2
  140. package/dist/features/scheduler/shared/scheduler-hour-grid.d.ts +2 -2
  141. package/dist/features/scheduler/shared/scheduler-hour-grid.js +3 -3
  142. package/dist/features/scheduler/shared/scheduler-month-view.d.ts +3 -3
  143. package/dist/features/scheduler/shared/scheduler-month-view.js +2 -2
  144. package/dist/features/table/table/table.types.d.ts +7 -3
  145. package/dist/features/table/table-action-bar/table-action-bar.js +3 -3
  146. package/dist/features/table/table-managed/table-managed.js +2 -2
  147. package/dist/i18n/en.d.ts +3 -0
  148. package/dist/i18n/en.js +3 -0
  149. package/dist/i18n/fr.js +3 -0
  150. package/dist/index.d.ts +3 -3
  151. package/dist/jsx.d.ts +1 -1
  152. package/dist/mixins/button.mixin.d.ts +10 -9
  153. package/dist/mixins/button.mixin.js +14 -6
  154. package/dist/mixins/card-link.mixin.d.ts +3 -2
  155. package/dist/mixins/card-link.mixin.js +12 -5
  156. package/dist/mixins/disabled.mixin.d.ts +3 -3
  157. package/dist/mixins/disabled.mixin.js +10 -5
  158. package/dist/mixins/field-appearance.mixin.d.ts +19 -0
  159. package/dist/mixins/{field-variant.mixin.js → field-appearance.mixin.js} +11 -11
  160. package/dist/ssr/animate-ssr.css +26 -72
  161. package/dist/ssr/links-ssr.css +35 -0
  162. package/dist/ssr/overlay-ssr.css +0 -41
  163. package/dist/ssr/rich-text.css +38 -0
  164. package/dist/ssr/sidebar-ssr.css +0 -38
  165. package/dist/ssr/toolbar-ssr.css +73 -39
  166. package/dist/types/variant.type.d.ts +26 -10
  167. package/dist/utils/navigation.utils.d.ts +14 -0
  168. package/dist/utils/navigation.utils.js +33 -0
  169. package/dist/utils/new-tab-anchor.controller.d.ts +44 -0
  170. package/dist/utils/new-tab-anchor.controller.js +102 -0
  171. package/dist/utils/severity-icon.utils.d.ts +1 -1
  172. package/dist/utils/severity-icon.utils.js +1 -1
  173. package/dist/utils/theme-generator.utils.d.ts +3 -1
  174. package/dist/utils/theme-generator.utils.js +3 -1
  175. package/package.json +8 -2
  176. package/dist/cdn/components/chunks/chunk-2JLDJFYD.js +0 -48
  177. package/dist/cdn/components/chunks/chunk-ATH2OAMU.js +0 -1
  178. package/dist/cdn/components/chunks/chunk-BKHD4S57.js +0 -1
  179. package/dist/cdn/components/chunks/chunk-T7WABJEJ.js +0 -1
  180. package/dist/cdn/components/chunks/chunk-XSMCT4DF.js +0 -1
  181. package/dist/mixins/field-variant.mixin.d.ts +0 -22
@@ -10,6 +10,7 @@ import { ifDefined } from "lit/directives/if-defined.js";
10
10
  import { ButtonMixin } from "../../../mixins/button.mixin.js";
11
11
  import { CommandMixin } from "../../../mixins/command.mixin.js";
12
12
  import { DisabledMixin } from "../../../mixins/disabled.mixin.js";
13
+ import { LocalizableMixin } from "../../../mixins/localizable.mixin.js";
13
14
  import { handleLinkNavigation } from "../../../utils/navigation.utils.js";
14
15
  import { fabStyles } from "./fab.styles.js";
15
16
  /**
@@ -18,7 +19,7 @@ import { fabStyles } from "./fab.styles.js";
18
19
  export const EunomiaFabTagName = "eun-fab";
19
20
  /**
20
21
  * A circular icon button for a screen's single primary action. It shares
21
- * its variant, size, href, loading, and keyboard behavior with the regular
22
+ * its color, size, href, loading, and keyboard behavior with the regular
22
23
  * button.
23
24
  *
24
25
  * When label is set, the button becomes extendable. It stays a plain circle
@@ -32,13 +33,14 @@ export const EunomiaFabTagName = "eun-fab";
32
33
  * @prop {EunomiaIconName} icon - The icon displayed at all times
33
34
  * @prop {string} label - An optional text label, revealed on hover or focus by the slide animation. Omit it for an icon only button
34
35
  * @prop {'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'} position - Fixes the button to a corner of the viewport. Left unset, the button follows the normal document flow
35
- * @prop {'primary' | 'secondary' | 'critical' | 'success' | 'warning'} [variant='primary'] - The color theme applied to the button
36
- * @prop {boolean} [fixedColor=false] - Keeps a light or dark button white or black in every theme. Without it, they follow the grey scale, which flips with the active theme. Has no effect on the other variants
37
- * @attr {boolean} [fixed-color=false] - Keeps a light or dark button white or black in every theme. Without it, they follow the grey scale, which flips with the active theme. Has no effect on the other variants
36
+ * @prop {'primary' | 'secondary' | 'critical' | 'success' | 'warning'} [color='primary'] - The color applied to the button
37
+ * @prop {boolean} [fixedColor=false] - Keeps a light or dark button white or black in every theme. Without it, they follow the grey scale, which flips with the active theme. Has no effect on the other colors
38
+ * @attr {boolean} [fixed-color=false] - Keeps a light or dark button white or black in every theme. Without it, they follow the grey scale, which flips with the active theme. Has no effect on the other colors
38
39
  * @prop {'xs' | 's' | 'm' | 'l' | 'xl'} [size='m'] - The size of the button
39
40
  * @prop {boolean} [disabled=false] - Whether the button is disabled. For a link button, this removes the href instead, since links have no native disabled state
40
41
  * @prop {string} href - The URL the button navigates to. When set, the button renders as a link instead of a button
41
42
  * @prop {'_blank' | '_self' | '_parent' | '_top'} target - Where to open the linked URL. Only used when href is set. Opening in a new tab automatically announces it to assistive technology, and adds an external link icon when label is also set
43
+ * @prop {string} [locale] - The locale used to resolve the "opens in a new tab" announcement. Defaults to the one set with setDefaultLocale, English otherwise
42
44
  * @prop {string} download - The filename used when the linked URL is downloaded instead of navigated to. Only used when href is set
43
45
  * @prop {boolean} [loading=false] - Whether the button shows a loading indicator. Like disabled, it blocks interaction, but keeps the icon and label in place so the button doesn't change size
44
46
  * @prop {string} [loadingLabel='Loading…'] - The accessible label announced while the button is loading
@@ -47,7 +49,7 @@ export const EunomiaFabTagName = "eun-fab";
47
49
  * @attr {string} aria-label - The accessible label for the button. Required when label isn't set, since an icon alone has no accessible name, and recommended even when it is if the label text alone doesn't describe the action
48
50
  * @fires click - Fired when the button is activated by mouse, Enter, or Space
49
51
  * @fires {NavigationEvent} eunnavigate - Fired before following href, so a client-side router can intercept the navigation. Not fired for downloads, and cancelable
50
- * @cssprop [--fab-background] - Sets the background color, for every variant
52
+ * @cssprop [--fab-background] - Sets the background color, for every color
51
53
  * @cssprop [--fab-background-hover] - Sets the background color on hover
52
54
  * @cssprop [--fab-text] - Sets the color of the text and icon
53
55
  * @cssprop [--fab-focus-outline] - Sets the color of the focus outline
@@ -56,7 +58,7 @@ export const EunomiaFabTagName = "eun-fab";
56
58
  * @cssprop [--fab-offset] - Sets the distance from the viewport edge for a positioned button
57
59
  * @element eun-fab
58
60
  */
59
- let EunomiaFab = class EunomiaFab extends ButtonMixin(CommandMixin(DisabledMixin(LitElement))) {
61
+ let EunomiaFab = class EunomiaFab extends ButtonMixin(CommandMixin(DisabledMixin(LocalizableMixin(LitElement)))) {
60
62
  constructor() {
61
63
  super(...arguments);
62
64
  /**
@@ -132,7 +134,8 @@ let EunomiaFab = class EunomiaFab extends ButtonMixin(CommandMixin(DisabledMixin
132
134
  if (!this.isBlank || this.ariaLabel) {
133
135
  return nothing;
134
136
  }
135
- return html `<span class="sr-only"> (opens in a new tab)</span>`;
137
+ const hint = this.dictionary.link.opensInNewTab;
138
+ return html `<span class="sr-only"> (${hint})</span>`;
136
139
  }
137
140
  /**
138
141
  * Renders `label`, with an appended external-link icon (mirroring
@@ -198,7 +201,7 @@ let EunomiaFab = class EunomiaFab extends ButtonMixin(CommandMixin(DisabledMixin
198
201
  rel=${ifDefined(this.isBlank ? "noopener noreferrer" : undefined)}
199
202
  download=${ifDefined(this.download)}
200
203
  aria-label=${ifDefined(this.isBlank && this.ariaLabel
201
- ? `${this.ariaLabel} (opens in a new tab)`
204
+ ? `${this.ariaLabel} (${this.dictionary.link.opensInNewTab})`
202
205
  : (this.ariaLabel ?? undefined))}
203
206
  aria-disabled=${ifDefined(this.disabled ? "true" : undefined)}
204
207
  aria-busy=${ifDefined(this.loading ? "true" : undefined)}
@@ -3,15 +3,15 @@ import { buttonPaletteStyles } from "../shared-styles/button-palette.styles.js";
3
3
  /**
4
4
  * Base style for the floating action button.
5
5
  *
6
- * The variant palette is shared with `eun-button` (see
6
+ * The color palette is shared with `eun-button` (see
7
7
  * {@link buttonPaletteStyles}) and read *behind* the `--fab-*` custom
8
- * properties, so those win over every variant. A fab only has a solid look,
8
+ * properties, so those win over every color. A fab only has a solid look,
9
9
  * so it only reads the solid slots.
10
10
  *
11
11
  * Custom css variables:
12
- * [--fab-background] - Set a custom background color, for every variant
12
+ * [--fab-background] - Set a custom background color, for every color
13
13
  * [--fab-background-hover] - Set a custom background color for hover state. Derived from --fab-background when only that one is set
14
- * [--fab-text] - Set a custom text/icon color, for every variant
14
+ * [--fab-text] - Set a custom text/icon color, for every color
15
15
  * [--fab-focus-outline] - Set a custom color to the focus outline
16
16
  * [--fab-transition] - Set a custom transition time, shared by the slide-open animation and the hover/focus color changes (mirrors `eun-button`'s own `--button-transition`)
17
17
  * [--fab-z-index] - Set a custom z-index for a positioned FAB
@@ -1,9 +1,10 @@
1
- import { LitElement, TemplateResult } from "lit";
1
+ import { LitElement, PropertyValues, TemplateResult } from "lit";
2
2
  import { EunomiaIconName } from "../../content/icon/icon.type.js";
3
3
  /**
4
4
  * Tag name of the link component.
5
5
  */
6
6
  export declare const EunomiaLinkTagName = "eun-link";
7
+ declare const EunomiaLink_base: import("../../../utils/constructor.type.js").Constructor<import("../../../mixins/localizable.mixin.js").LocalizableMixinInterface> & typeof LitElement;
7
8
  /**
8
9
  * A basic link with an optional decorative leading icon.
9
10
  *
@@ -11,14 +12,22 @@ export declare const EunomiaLinkTagName = "eun-link";
11
12
  * prevent the new page from controlling the original one, without affecting
12
13
  * same-tab navigation.
13
14
  *
14
- * @slot [default] - The link content
15
+ * Without href, a native anchor placed as a direct child becomes the link
16
+ * instead, such as a framework's own link component. The anchor then exists
17
+ * in the server-rendered HTML, so crawlers that don't run JavaScript still
18
+ * find it, and it keeps the link styles and the current state. When that
19
+ * anchor opens in a new tab, the link shows the same end icon and announces
20
+ * it the same way, through the anchor's aria-description.
21
+ *
22
+ * @slot [default] - The link content, or a native anchor carrying the href itself when href is unset
15
23
  * @slot [icon] - Replaces the leading icon entirely with any content, not just an icon element. Falls back to icon when empty
16
24
  * @prop {string} href - The destination URL
17
- * @prop {'_blank' | '_self' | '_parent' | '_top'} [target='_self'] - Where to open the linked URL. Opening in a new tab automatically announces it to assistive technology
25
+ * @prop {'_blank' | '_self' | '_parent' | '_top'} [target='_self'] - Where to open the linked URL. Opening in a new tab automatically adds an external link icon at the end and announces it to assistive technology, in the current locale
18
26
  * @prop {import('../../content/icon/icon.type.js').EunomiaIconName} icon - An optional decorative icon displayed to the left of the link
19
27
  * @prop {'light' | 'dark'} [theme='light'] - The text color theme. Use dark on a dark background
20
28
  * @prop {'auto' | 'off' | 'on'} [visitedStyle='auto'] - Controls the visited state color. Auto follows the browser's real navigation history, off always renders as unvisited, and on always renders as visited
21
29
  * @prop {boolean} [current=false] - Marks the link as the current page: it renders with aria-current="page" and keeps the --link-color-current color in every state (hover, focus, visited), for example an active item in a navigation
30
+ * @prop {string} [locale] - The locale used to resolve the "opens in a new tab" announcement. Defaults to the one set with setDefaultLocale, English otherwise
22
31
  * @attr {string} aria-label - An accessible label that overrides the link content. When the link also opens in a new tab, a note announcing it is appended automatically
23
32
  * @fires {NavigationEvent} eunnavigate - Fired before following the link, so a client-side router can intercept the navigation. Cancelable
24
33
  * @cssprop [--link-color] - Sets the color of the link
@@ -37,7 +46,8 @@ export declare const EunomiaLinkTagName = "eun-link";
37
46
  * @cssstate focus-visible - Set while the link is keyboard-focused (never after a mouse click), so it can be styled from outside the shadow root with eun-link:state(focus-visible). Turbopack's CSS parser (lightningcss) doesn't recognize :state() yet and rejects the whole stylesheet it's in ; ship that rule as a runtime <style>/<link> instead until it's supported
38
47
  * @element eun-link
39
48
  */
40
- export declare class EunomiaLink extends LitElement {
49
+ export declare class EunomiaLink extends EunomiaLink_base {
50
+ #private;
41
51
  /**
42
52
  * Styles to import to the component.
43
53
  *
@@ -60,14 +70,17 @@ export declare class EunomiaLink extends LitElement {
60
70
  */
61
71
  icon?: EunomiaIconName;
62
72
  /**
63
- * Text color theme, use `dark` on a dark background.
73
+ * Text color theme, use `dark` on a dark background. Only reflected once
74
+ * it differs from the default, so an SSR framework doesn't see an
75
+ * attribute it never rendered.
64
76
  * @default light
65
77
  */
66
78
  theme: "light" | "dark";
67
79
  /**
68
80
  * Controls the visited-state color: `auto` follows the browser's real
69
81
  * navigation history for `href`, `off` always renders as unvisited, `on`
70
- * always renders as visited.
82
+ * always renders as visited. Only reflected once it differs from the
83
+ * default, like {@link theme}.
71
84
  * @default auto
72
85
  */
73
86
  visitedStyle: "auto" | "off" | "on";
@@ -101,6 +114,45 @@ export declare class EunomiaLink extends LitElement {
101
114
  */
102
115
  static get observedAttributes(): string[];
103
116
  attributeChangedCallback(name: string, oldValue: string | null, value: string | null): void;
117
+ /**
118
+ * The native `<a>` slotted as a direct child while `href` is unset, which
119
+ * then acts as the link itself : it's in the light DOM, so it's part of
120
+ * the server-rendered HTML, and a framework's own link component (Next's
121
+ * `<Link>`, Angular's `routerLink`, ...) can be that anchor. Navigation,
122
+ * `target`/`rel` and the new-tab hint are the consumer's own in this mode.
123
+ * @internal
124
+ */
125
+ private get slottedAnchor();
126
+ connectedCallback(): void;
127
+ disconnectedCallback(): void;
128
+ protected updated(changed: PropertyValues): void;
129
+ /**
130
+ * Mirrors {@link current} onto the slotted anchor as `aria-current`,
131
+ * since that anchor, not the wrapper around it, is what assistive
132
+ * technology announces.
133
+ * @private
134
+ */
135
+ private syncSlottedAnchor;
136
+ /**
137
+ * Re-renders when the default slot changes, so an anchor added or removed
138
+ * after the first render switches between the two modes.
139
+ * @private
140
+ */
141
+ private handleDefaultSlotChange;
142
+ /**
143
+ * The slotted anchor's counterpart of {@link handleFocus} : it lives in
144
+ * the light DOM, so its focus reaches the host as a plain `focusin`.
145
+ * @param event the host's focusin event.
146
+ * @private
147
+ */
148
+ private handleSlottedFocusIn;
149
+ /**
150
+ * Clears the `focus-visible` custom state once the slotted anchor loses
151
+ * focus.
152
+ * @param event the host's focusout event.
153
+ * @private
154
+ */
155
+ private handleSlottedFocusOut;
104
156
  /**
105
157
  * Elements assigned to the `icon` slot, if any ; queried live so
106
158
  * {@link render} can decide whether to fall back to the `icon` prop.
@@ -141,6 +193,12 @@ export declare class EunomiaLink extends LitElement {
141
193
  * @private
142
194
  */
143
195
  private handleClick;
196
+ /**
197
+ * The decorative icon at the end of a link opening in a new tab, the
198
+ * wording being announced separately.
199
+ * @private
200
+ */
201
+ private renderExternalLinkIcon;
144
202
  /**
145
203
  * Renders the link template.
146
204
  * @returns The rendered HTML template
@@ -152,3 +210,4 @@ declare global {
152
210
  [EunomiaLinkTagName]: EunomiaLink;
153
211
  }
154
212
  }
213
+ export {};
@@ -4,12 +4,26 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
13
+ if (kind === "m") throw new TypeError("Private method is not writable");
14
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
15
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
16
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
17
+ };
18
+ var _EunomiaLink_currentAnchor, _EunomiaLink_newTab;
7
19
  import { customElement, property, queryAssignedElements, } from "lit/decorators.js";
8
20
  import { html, LitElement, nothing } from "lit";
9
21
  import { classMap } from "lit/directives/class-map.js";
10
22
  import { ifDefined } from "lit/directives/if-defined.js";
11
23
  import { linkStyles } from "./link.styles.js";
12
- import { handleLinkNavigation } from "../../../utils/navigation.utils.js";
24
+ import { handleLinkNavigation, warnIgnoredHostLinkAttributes, } from "../../../utils/navigation.utils.js";
25
+ import { LocalizableMixin } from "../../../mixins/localizable.mixin.js";
26
+ import { NewTabAnchorController } from "../../../utils/new-tab-anchor.controller.js";
13
27
  /**
14
28
  * Tag name of the link component.
15
29
  */
@@ -21,14 +35,22 @@ export const EunomiaLinkTagName = "eun-link";
21
35
  * prevent the new page from controlling the original one, without affecting
22
36
  * same-tab navigation.
23
37
  *
24
- * @slot [default] - The link content
38
+ * Without href, a native anchor placed as a direct child becomes the link
39
+ * instead, such as a framework's own link component. The anchor then exists
40
+ * in the server-rendered HTML, so crawlers that don't run JavaScript still
41
+ * find it, and it keeps the link styles and the current state. When that
42
+ * anchor opens in a new tab, the link shows the same end icon and announces
43
+ * it the same way, through the anchor's aria-description.
44
+ *
45
+ * @slot [default] - The link content, or a native anchor carrying the href itself when href is unset
25
46
  * @slot [icon] - Replaces the leading icon entirely with any content, not just an icon element. Falls back to icon when empty
26
47
  * @prop {string} href - The destination URL
27
- * @prop {'_blank' | '_self' | '_parent' | '_top'} [target='_self'] - Where to open the linked URL. Opening in a new tab automatically announces it to assistive technology
48
+ * @prop {'_blank' | '_self' | '_parent' | '_top'} [target='_self'] - Where to open the linked URL. Opening in a new tab automatically adds an external link icon at the end and announces it to assistive technology, in the current locale
28
49
  * @prop {import('../../content/icon/icon.type.js').EunomiaIconName} icon - An optional decorative icon displayed to the left of the link
29
50
  * @prop {'light' | 'dark'} [theme='light'] - The text color theme. Use dark on a dark background
30
51
  * @prop {'auto' | 'off' | 'on'} [visitedStyle='auto'] - Controls the visited state color. Auto follows the browser's real navigation history, off always renders as unvisited, and on always renders as visited
31
52
  * @prop {boolean} [current=false] - Marks the link as the current page: it renders with aria-current="page" and keeps the --link-color-current color in every state (hover, focus, visited), for example an active item in a navigation
53
+ * @prop {string} [locale] - The locale used to resolve the "opens in a new tab" announcement. Defaults to the one set with setDefaultLocale, English otherwise
32
54
  * @attr {string} aria-label - An accessible label that overrides the link content. When the link also opens in a new tab, a note announcing it is appended automatically
33
55
  * @fires {NavigationEvent} eunnavigate - Fired before following the link, so a client-side router can intercept the navigation. Cancelable
34
56
  * @cssprop [--link-color] - Sets the color of the link
@@ -47,7 +69,7 @@ export const EunomiaLinkTagName = "eun-link";
47
69
  * @cssstate focus-visible - Set while the link is keyboard-focused (never after a mouse click), so it can be styled from outside the shadow root with eun-link:state(focus-visible). Turbopack's CSS parser (lightningcss) doesn't recognize :state() yet and rejects the whole stylesheet it's in ; ship that rule as a runtime <style>/<link> instead until it's supported
48
70
  * @element eun-link
49
71
  */
50
- let EunomiaLink = class EunomiaLink extends LitElement {
72
+ let EunomiaLink = class EunomiaLink extends LocalizableMixin(LitElement) {
51
73
  /**
52
74
  * The element's custom state set, typed locally since the bundled DOM
53
75
  * typings don't declare `ElementInternals.states` yet.
@@ -64,14 +86,17 @@ let EunomiaLink = class EunomiaLink extends LitElement {
64
86
  */
65
87
  this.target = "_self";
66
88
  /**
67
- * Text color theme, use `dark` on a dark background.
89
+ * Text color theme, use `dark` on a dark background. Only reflected once
90
+ * it differs from the default, so an SSR framework doesn't see an
91
+ * attribute it never rendered.
68
92
  * @default light
69
93
  */
70
94
  this.theme = "light";
71
95
  /**
72
96
  * Controls the visited-state color: `auto` follows the browser's real
73
97
  * navigation history for `href`, `off` always renders as unvisited, `on`
74
- * always renders as visited.
98
+ * always renders as visited. Only reflected once it differs from the
99
+ * default, like {@link theme}.
75
100
  * @default auto
76
101
  */
77
102
  this.visitedStyle = "auto";
@@ -82,6 +107,48 @@ let EunomiaLink = class EunomiaLink extends LitElement {
82
107
  * @default false
83
108
  */
84
109
  this.current = false;
110
+ /**
111
+ * The anchor {@link current} was last mirrored onto, so its
112
+ * `aria-current` can be cleared once it's swapped out or removed.
113
+ * @internal
114
+ */
115
+ _EunomiaLink_currentAnchor.set(this, void 0);
116
+ /**
117
+ * Announces the slotted anchor opening in a new tab.
118
+ * @internal
119
+ */
120
+ _EunomiaLink_newTab.set(this, new NewTabAnchorController(this, () => this.slottedAnchor, () => this.dictionary.link.opensInNewTab));
121
+ /**
122
+ * Re-renders when the default slot changes, so an anchor added or removed
123
+ * after the first render switches between the two modes.
124
+ * @private
125
+ */
126
+ this.handleDefaultSlotChange = () => {
127
+ this.requestUpdate();
128
+ };
129
+ /**
130
+ * The slotted anchor's counterpart of {@link handleFocus} : it lives in
131
+ * the light DOM, so its focus reaches the host as a plain `focusin`.
132
+ * @param event the host's focusin event.
133
+ * @private
134
+ */
135
+ this.handleSlottedFocusIn = (event) => {
136
+ const target = event.target;
137
+ if (target === this.slottedAnchor && target.matches(":focus-visible")) {
138
+ this.states?.add("focus-visible");
139
+ }
140
+ };
141
+ /**
142
+ * Clears the `focus-visible` custom state once the slotted anchor loses
143
+ * focus.
144
+ * @param event the host's focusout event.
145
+ * @private
146
+ */
147
+ this.handleSlottedFocusOut = (event) => {
148
+ if (event.target === this.slottedAnchor) {
149
+ this.states?.delete("focus-visible");
150
+ }
151
+ };
85
152
  /**
86
153
  * Re-renders when the `icon` slot's assigned content changes, so the
87
154
  * `icon` prop fallback correctly appears/disappears as content is
@@ -113,6 +180,56 @@ let EunomiaLink = class EunomiaLink extends LitElement {
113
180
  }
114
181
  super.attributeChangedCallback(name, oldValue, value);
115
182
  }
183
+ /**
184
+ * The native `<a>` slotted as a direct child while `href` is unset, which
185
+ * then acts as the link itself : it's in the light DOM, so it's part of
186
+ * the server-rendered HTML, and a framework's own link component (Next's
187
+ * `<Link>`, Angular's `routerLink`, ...) can be that anchor. Navigation,
188
+ * `target`/`rel` and the new-tab hint are the consumer's own in this mode.
189
+ * @internal
190
+ */
191
+ get slottedAnchor() {
192
+ return this.href ? null : this.querySelector(":scope > a");
193
+ }
194
+ connectedCallback() {
195
+ super.connectedCallback();
196
+ this.addEventListener("focusin", this.handleSlottedFocusIn);
197
+ this.addEventListener("focusout", this.handleSlottedFocusOut);
198
+ }
199
+ disconnectedCallback() {
200
+ this.removeEventListener("focusin", this.handleSlottedFocusIn);
201
+ this.removeEventListener("focusout", this.handleSlottedFocusOut);
202
+ super.disconnectedCallback();
203
+ }
204
+ updated(changed) {
205
+ super.updated(changed);
206
+ this.syncSlottedAnchor();
207
+ }
208
+ /**
209
+ * Mirrors {@link current} onto the slotted anchor as `aria-current`,
210
+ * since that anchor, not the wrapper around it, is what assistive
211
+ * technology announces.
212
+ * @private
213
+ */
214
+ syncSlottedAnchor() {
215
+ const anchor = this.slottedAnchor;
216
+ if (__classPrivateFieldGet(this, _EunomiaLink_currentAnchor, "f") && __classPrivateFieldGet(this, _EunomiaLink_currentAnchor, "f") !== anchor) {
217
+ __classPrivateFieldGet(this, _EunomiaLink_currentAnchor, "f").removeAttribute("aria-current");
218
+ __classPrivateFieldSet(this, _EunomiaLink_currentAnchor, undefined, "f");
219
+ }
220
+ if (!anchor) {
221
+ return;
222
+ }
223
+ warnIgnoredHostLinkAttributes(this);
224
+ if (this.current) {
225
+ anchor.setAttribute("aria-current", "page");
226
+ __classPrivateFieldSet(this, _EunomiaLink_currentAnchor, anchor, "f");
227
+ }
228
+ else if (__classPrivateFieldGet(this, _EunomiaLink_currentAnchor, "f") === anchor) {
229
+ anchor.removeAttribute("aria-current");
230
+ __classPrivateFieldSet(this, _EunomiaLink_currentAnchor, undefined, "f");
231
+ }
232
+ }
116
233
  /**
117
234
  * Mirrors the inner anchor's real `:focus-visible` state onto the host as
118
235
  * the `focus-visible` custom state, styled from outside with
@@ -147,6 +264,16 @@ let EunomiaLink = class EunomiaLink extends LitElement {
147
264
  handleClick(event) {
148
265
  handleLinkNavigation(this, event, this.href, this.target);
149
266
  }
267
+ /**
268
+ * The decorative icon at the end of a link opening in a new tab, the
269
+ * wording being announced separately.
270
+ * @private
271
+ */
272
+ renderExternalLinkIcon() {
273
+ return html `<span id="external-link-icon">
274
+ <eun-icon name="arrow_forward" shape="round" size="14"></eun-icon>
275
+ </span>`;
276
+ }
150
277
  /**
151
278
  * Renders the link template.
152
279
  * @returns The rendered HTML template
@@ -154,6 +281,29 @@ let EunomiaLink = class EunomiaLink extends LitElement {
154
281
  render() {
155
282
  const isBlank = this.target === "_blank";
156
283
  const hasIcon = !!this.icon || this.iconSlotEls?.length > 0;
284
+ const iconSlot = html `<slot
285
+ name="icon"
286
+ class=${classMap({ "has-icon": hasIcon })}
287
+ @slotchange=${this.handleIconSlotChange}
288
+ >
289
+ ${this.icon
290
+ ? html `<eun-icon name=${this.icon} size="18"></eun-icon>`
291
+ : nothing}
292
+ </slot>`;
293
+ if (this.slottedAnchor) {
294
+ return html `
295
+ <span
296
+ id="link"
297
+ part="link"
298
+ class=${classMap({ "has-icon": hasIcon, "slotted-link": true })}
299
+ >
300
+ ${iconSlot}
301
+ <slot @slotchange=${this.handleDefaultSlotChange}></slot>
302
+ ${__classPrivateFieldGet(this, _EunomiaLink_newTab, "f").isBlank ? this.renderExternalLinkIcon() : nothing}
303
+ </span>
304
+ `;
305
+ }
306
+ const newTabHint = `(${this.dictionary.link.opensInNewTab})`;
157
307
  return html `
158
308
  <a
159
309
  id="link"
@@ -164,29 +314,24 @@ let EunomiaLink = class EunomiaLink extends LitElement {
164
314
  aria-current=${ifDefined(this.current ? "page" : undefined)}
165
315
  rel=${ifDefined(isBlank ? "noopener noreferrer" : undefined)}
166
316
  aria-label=${ifDefined(isBlank && this.ariaLabel
167
- ? `${this.ariaLabel} (opens in a new tab)`
317
+ ? `${this.ariaLabel} ${newTabHint}`
168
318
  : (this.ariaLabel ?? undefined))}
169
319
  @click=${this.handleClick}
170
320
  @focus=${this.handleFocus}
171
321
  @blur=${this.handleBlur}
172
322
  >
173
- <slot
174
- name="icon"
175
- class=${classMap({ "has-icon": hasIcon })}
176
- @slotchange=${this.handleIconSlotChange}
177
- >
178
- ${this.icon
179
- ? html `<eun-icon name=${this.icon} size="18"></eun-icon>`
180
- : nothing}
181
- </slot>
182
- <slot></slot>
323
+ ${iconSlot}
324
+ <slot @slotchange=${this.handleDefaultSlotChange}></slot>
183
325
  ${isBlank && !this.ariaLabel
184
- ? html `<span class="sr-only"> (opens in a new tab)</span>`
326
+ ? html `<span class="sr-only"> ${newTabHint}</span>`
185
327
  : nothing}
328
+ ${isBlank ? this.renderExternalLinkIcon() : nothing}
186
329
  </a>
187
330
  `;
188
331
  }
189
332
  };
333
+ _EunomiaLink_currentAnchor = new WeakMap();
334
+ _EunomiaLink_newTab = new WeakMap();
190
335
  /**
191
336
  * Styles to import to the component.
192
337
  *
@@ -205,10 +350,21 @@ __decorate([
205
350
  property()
206
351
  ], EunomiaLink.prototype, "icon", void 0);
207
352
  __decorate([
208
- property({ reflect: true })
353
+ property({
354
+ reflect: true,
355
+ converter: {
356
+ toAttribute: (value) => (value === "light" ? null : value),
357
+ },
358
+ })
209
359
  ], EunomiaLink.prototype, "theme", void 0);
210
360
  __decorate([
211
- property({ reflect: true, attribute: "visited-style" })
361
+ property({
362
+ reflect: true,
363
+ attribute: "visited-style",
364
+ converter: {
365
+ toAttribute: (value) => (value === "auto" ? null : value),
366
+ },
367
+ })
212
368
  ], EunomiaLink.prototype, "visitedStyle", void 0);
213
369
  __decorate([
214
370
  property({ type: Boolean, reflect: true })
@@ -65,6 +65,12 @@ const base = css `
65
65
  display: inline-flex;
66
66
  flex-shrink: 0;
67
67
  }
68
+ #external-link-icon {
69
+ display: inline-flex;
70
+ flex-shrink: 0;
71
+ margin-inline-start: calc(-1 * var(--eun-space-xs, 4px));
72
+ transform: rotate(-45deg);
73
+ }
68
74
  #link {
69
75
  display: inline-flex;
70
76
  align-items: center;
@@ -134,8 +140,87 @@ const base = css `
134
140
  --icon-color: var(--link-icon-color, var(--_colorCurrent));
135
141
  }
136
142
 
143
+ #link.slotted-link {
144
+ position: relative;
145
+ text-decoration: none;
146
+ cursor: auto;
147
+ }
148
+ ::slotted(a)::after {
149
+ content: "";
150
+ position: absolute;
151
+ inset: 0;
152
+ border-radius: inherit;
153
+ }
154
+ ::slotted(a) {
155
+ color: var(--_color);
156
+ font: inherit;
157
+ text-decoration: var(--_textDecoration);
158
+ text-wrap: pretty;
159
+ border-radius: var(--link-border-radius, var(--eun-border-radius-m, 8px));
160
+ transition:
161
+ color 0.3s ease-out,
162
+ background-color 0.3s ease-out,
163
+ text-decoration-color 0.3s ease-out;
164
+ }
165
+ ::slotted(a:visited) {
166
+ color: var(--_colorVisited);
167
+ text-decoration-color: var(--_colorVisited);
168
+ }
169
+ ::slotted(a:visited:hover) {
170
+ color: var(--_colorVisitedHover);
171
+ text-decoration-color: var(--_colorVisitedHover);
172
+ }
173
+ :host([visited-style="off"]) ::slotted(a:visited) {
174
+ color: var(--_color);
175
+ text-decoration-color: currentColor;
176
+ }
177
+ :host([visited-style="off"]) ::slotted(a:visited:hover) {
178
+ color: var(--_colorHover);
179
+ text-decoration-color: currentColor;
180
+ }
181
+ :host([visited-style="on"]) ::slotted(a) {
182
+ color: var(--_colorVisited);
183
+ text-decoration-color: var(--_colorVisited);
184
+ }
185
+ :host([visited-style="on"]) ::slotted(a:hover) {
186
+ color: var(--_colorVisitedHover);
187
+ text-decoration-color: var(--_colorVisitedHover);
188
+ }
189
+ ::slotted(a:hover) {
190
+ color: var(--_colorHover);
191
+ }
192
+ #link.slotted-link:hover {
193
+ --icon-color: var(--link-icon-color, var(--_colorHover));
194
+ }
195
+ ::slotted(a:focus-visible) {
196
+ color: var(--_colorHover) !important;
197
+ text-decoration: none;
198
+ outline: none !important;
199
+ }
200
+ :host(:state(focus-visible)) #link.slotted-link {
201
+ background: var(
202
+ --link-background-focus,
203
+ var(--link-background, transparent)
204
+ );
205
+ outline-color: var(--_colorFocus);
206
+ outline-offset: 2px;
207
+ --icon-color: var(--link-icon-color, var(--_colorHover));
208
+ }
209
+ :host([current]) ::slotted(a),
210
+ :host([current]) ::slotted(a:visited),
211
+ :host([current]) ::slotted(a:hover),
212
+ :host([current]) ::slotted(a:visited:hover),
213
+ :host([current]) ::slotted(a:focus-visible) {
214
+ color: var(--_colorCurrent) !important;
215
+ text-decoration-color: var(--_colorCurrent);
216
+ }
217
+ :host([current]) #link.slotted-link {
218
+ --icon-color: var(--link-icon-color, var(--_colorCurrent));
219
+ }
220
+
137
221
  @media (prefers-reduced-motion: reduce) {
138
- #link {
222
+ #link,
223
+ ::slotted(a) {
139
224
  transition: none;
140
225
  }
141
226
  }
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Shared color palette of the button family (`eun-button`, `eun-fab`, and so
3
- * `eun-dropdown-button`, which renders an `eun-button`). A `variant` fills
3
+ * `eun-dropdown-button`, which renders an `eun-button`). A `color` fills
4
4
  * the same slots on the host, and each component's stylesheet then reads
5
5
  * them, per appearance and state, *behind* its public `--<component>-*`
6
- * custom properties, so those always win over any variant and appearance.
6
+ * custom properties, so those always win over any color and appearance.
7
7
  *
8
8
  * Slots :
9
9
  * - `--_solid`, `--_solid-hover`, `--_solid-active` : the strong color, at
@@ -20,7 +20,7 @@
20
20
  * `light` and `dark` are absolute colors : white and black, whatever the
21
21
  * appearance. They have no pale wash, so the tinted surface is as solid as
22
22
  * the strong one, and `flat` is a faint wash of the color itself rather than
23
- * the neutral surface every other variant shares. By default they follow the
23
+ * the neutral surface every other color shares. By default they follow the
24
24
  * grey scale, which flips with the active theme ; `fixed-color` pins them to
25
25
  * the light theme's white and black instead.
26
26
  */