@happyvertical/smrt-ui 0.34.1 → 0.34.3

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.
@@ -32,6 +32,15 @@ export interface Props extends Omit<HTMLButtonAttributes, 'class' | 'href'> {
32
32
  * own CSS (issue #1589) — the base button styling still applies.
33
33
  */
34
34
  class?: string;
35
+ /**
36
+ * Anchor-mode attributes (link mode only, i.e. when `href` is set). The
37
+ * component already spreads these onto the rendered `<a>`; they are declared
38
+ * here so callers migrating an external `<a target="_blank" rel="...">` to
39
+ * Button (issue #1589) keep them type-checked. Ignored in button mode.
40
+ */
41
+ target?: HTMLAnchorAttributes['target'];
42
+ rel?: HTMLAnchorAttributes['rel'];
43
+ download?: HTMLAnchorAttributes['download'];
35
44
  }
36
45
 
37
46
  const {
@@ -45,6 +54,12 @@ const {
45
54
  class: className = '',
46
55
  onclick,
47
56
  children,
57
+ // Anchor-mode attributes pulled out of `rest` so they are only ever applied
58
+ // in link mode below — in button mode they are dropped, matching the doc and
59
+ // avoiding invalid `<button target=...>` HTML (PR #1608 review).
60
+ target,
61
+ rel,
62
+ download,
48
63
  ...rest
49
64
  }: Props = $props();
50
65
 
@@ -86,6 +101,9 @@ const linkProps = $derived(() => {
86
101
  {#if isLink}
87
102
  <a
88
103
  href={isDisabled ? undefined : href}
104
+ {target}
105
+ {rel}
106
+ {download}
89
107
  class="button {variant} {size} {className}"
90
108
  class:disabled={isDisabled}
91
109
  class:full-width={fullWidth}
@@ -5,7 +5,7 @@
5
5
  * renders as an anchor tag. Otherwise renders as a button.
6
6
  */
7
7
  import type { Snippet } from 'svelte';
8
- import type { HTMLButtonAttributes } from 'svelte/elements';
8
+ import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
9
9
  import type { ButtonSize, ButtonVariant } from '../../types-generic';
10
10
  /** Props for Button component */
11
11
  export interface Props extends Omit<HTMLButtonAttributes, 'class' | 'href'> {
@@ -27,6 +27,15 @@ export interface Props extends Omit<HTMLButtonAttributes, 'class' | 'href'> {
27
27
  * own CSS (issue #1589) — the base button styling still applies.
28
28
  */
29
29
  class?: string;
30
+ /**
31
+ * Anchor-mode attributes (link mode only, i.e. when `href` is set). The
32
+ * component already spreads these onto the rendered `<a>`; they are declared
33
+ * here so callers migrating an external `<a target="_blank" rel="...">` to
34
+ * Button (issue #1589) keep them type-checked. Ignored in button mode.
35
+ */
36
+ target?: HTMLAnchorAttributes['target'];
37
+ rel?: HTMLAnchorAttributes['rel'];
38
+ download?: HTMLAnchorAttributes['download'];
30
39
  }
31
40
  declare const Button: import("svelte").Component<Props, {}, "">;
32
41
  type Button = ReturnType<typeof Button>;
@@ -1 +1 @@
1
- {"version":3,"file":"Button.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/ui/Button.svelte.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAEV,oBAAoB,EACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGrE,iCAAiC;AACjC,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,MAAM,CAAC;IACzE,qBAAqB;IACrB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,mBAAmB;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wBAAwB;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAiFD,QAAA,MAAM,MAAM,2CAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"Button.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/ui/Button.svelte.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EACV,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGrE,iCAAiC;AACjC,MAAM,WAAW,KAAM,SAAQ,IAAI,CAAC,oBAAoB,EAAE,OAAO,GAAG,MAAM,CAAC;IACzE,qBAAqB;IACrB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,mBAAmB;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wBAAwB;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACxC,GAAG,CAAC,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,oBAAoB,CAAC,UAAU,CAAC,CAAC;CAC7C;AAuFD,QAAA,MAAM,MAAM,2CAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
@@ -83,6 +83,36 @@ describe('Button', () => {
83
83
  expect(link).toHaveClass('nav-link');
84
84
  expect(link).toHaveClass('button');
85
85
  });
86
+ it('passes target/rel through to the anchor in link mode (#1589)', () => {
87
+ // External links migrated from `<a target="_blank" rel="...">` to Button
88
+ // must keep their new-tab + opener-isolation semantics on the rendered <a>.
89
+ render(Button, {
90
+ props: {
91
+ children: textSnippet('Open'),
92
+ href: 'https://example.com',
93
+ target: '_blank',
94
+ rel: 'noreferrer',
95
+ },
96
+ });
97
+ const link = screen.getByRole('link', { name: 'Open' });
98
+ expect(link).toHaveAttribute('target', '_blank');
99
+ expect(link).toHaveAttribute('rel', 'noreferrer');
100
+ });
101
+ it('drops anchor-only attrs in button mode (no invalid <button target>) (#1608)', () => {
102
+ // In button mode the anchor-only props must NOT reach the <button> — they
103
+ // are pulled out of `rest` so the runtime matches the "ignored in button
104
+ // mode" doc and never emits invalid HTML.
105
+ render(Button, {
106
+ props: {
107
+ children: textSnippet('Save'),
108
+ target: '_blank',
109
+ rel: 'noreferrer',
110
+ },
111
+ });
112
+ const button = screen.getByRole('button', { name: 'Save' });
113
+ expect(button).not.toHaveAttribute('target');
114
+ expect(button).not.toHaveAttribute('rel');
115
+ });
86
116
  it('is not navigable when disabled in link mode', async () => {
87
117
  const { container } = render(Button, {
88
118
  props: { children: textSnippet('Home'), href: '/home', disabled: true },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-ui",
3
- "version": "0.34.1",
3
+ "version": "0.34.3",
4
4
  "description": "Domain-agnostic Svelte 5 UI runtime for SMRT: primitives, i18n client, theme system, and module UI registry",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -108,7 +108,7 @@
108
108
  },
109
109
  "dependencies": {
110
110
  "esm-env": "^1.2.2",
111
- "@happyvertical/smrt-types": "0.34.1"
111
+ "@happyvertical/smrt-types": "0.34.3"
112
112
  },
113
113
  "peerDependencies": {
114
114
  "svelte": "^5.18.2"