@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,
|
|
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.
|
|
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.
|
|
111
|
+
"@happyvertical/smrt-types": "0.34.3"
|
|
112
112
|
},
|
|
113
113
|
"peerDependencies": {
|
|
114
114
|
"svelte": "^5.18.2"
|