@juspay/svelte-ui-components 2.118.1 → 2.119.0

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.
@@ -8,6 +8,9 @@
8
8
  classes,
9
9
  testId,
10
10
  onclick,
11
+ href,
12
+ target,
13
+ rel,
11
14
  headerRight,
12
15
  footer,
13
16
  stretch = false,
@@ -15,7 +18,14 @@
15
18
  cssVars
16
19
  }: CardProperties = $props();
17
20
 
18
- const isInteractive = $derived(typeof onclick === 'function');
21
+ const isAnchor = $derived(typeof href === 'string' && href.length > 0);
22
+ const isInteractive = $derived(isAnchor || typeof onclick === 'function');
23
+ // HTML target values are ASCII case-insensitive (e.g. "_BLANK" opens a new
24
+ // context identically to "_blank"), so the comparison must normalize case
25
+ // to keep the noopener/noreferrer default from being silently skipped.
26
+ const resolvedRel = $derived(
27
+ rel ?? (target?.toLowerCase() === '_blank' ? 'noopener noreferrer' : null)
28
+ );
19
29
 
20
30
  const styleAttr = $derived(
21
31
  cssVars
@@ -37,8 +47,8 @@
37
47
  };
38
48
  </script>
39
49
 
40
- <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
41
- <div
50
+ <svelte:element
51
+ this={isAnchor ? 'a' : 'div'}
42
52
  class="card {classes ?? ''}"
43
53
  class:card-interactive={isInteractive}
44
54
  class:card-stretch={stretch}
@@ -46,10 +56,13 @@
46
56
  style={styleAttr}
47
57
  data-pw={typeof testId === 'string' ? testId : null}
48
58
  testID={typeof testId === 'string' ? testId : null}
49
- role={isInteractive ? 'button' : null}
50
- tabindex={isInteractive ? 0 : null}
51
- onclick={isInteractive ? onclick : null}
52
- onkeydown={isInteractive ? handleKeydown : null}
59
+ role={!isAnchor && isInteractive ? 'button' : null}
60
+ tabindex={!isAnchor && isInteractive ? 0 : null}
61
+ href={isAnchor ? href : null}
62
+ target={isAnchor ? target : null}
63
+ rel={isAnchor ? resolvedRel : null}
64
+ onclick={onclick ?? null}
65
+ onkeydown={!isAnchor && isInteractive ? handleKeydown : null}
53
66
  >
54
67
  {#if (typeof title === 'string' && title.length > 0) || typeof headerRight === 'function'}
55
68
  <div class="card-header" class:card-header-split={typeof headerRight === 'function'}>
@@ -85,10 +98,16 @@
85
98
  {@render footer()}
86
99
  </footer>
87
100
  {/if}
88
- </div>
101
+ </svelte:element>
89
102
 
90
103
  <style>
91
104
  .card {
105
+ /* Explicit so the root's layout doesn't depend on the browser default for
106
+ whichever tag svelte:element renders -- a <div> defaults to block and an
107
+ <a> defaults to inline, and an inline box ignores width/height entirely.
108
+ Pinning both here keeps anchor-mode cards byte-identical to div cards. */
109
+ display: block;
110
+ text-decoration: none;
92
111
  background: var(--card-background, inherit);
93
112
  border: var(--card-border, 1px solid currentColor);
94
113
  border-radius: var(--card-border-radius, var(--radius, 4px));
@@ -12,12 +12,28 @@ export type OptionalCardProperties = {
12
12
  /** Renders as `data-pw` on the root element for Playwright test selection. */
13
13
  testId?: string;
14
14
  /**
15
- * When provided the root element becomes interactive:
15
+ * When provided (and `href` is not) the root element becomes an interactive `<div>`:
16
16
  * `role="button"`, `tabindex=0`, and keydown (Enter/Space) triggers the handler.
17
- * When omitted the root is a plain `<div>` with no interactive attributes,
18
- * so existing consumers see zero behaviour change.
17
+ * When `href` is also provided, the root renders as a native `<a>` instead (see
18
+ * `href`) and this synthetic role/tabindex/keydown shim is skipped, since anchor
19
+ * semantics already cover focus and Enter-activation; `onclick` still fires either
20
+ * way. When neither `onclick` nor `href` is provided the root is a plain `<div>`
21
+ * with no interactive attributes, so existing consumers see zero behaviour change.
19
22
  */
20
23
  onclick?: (event: MouseEvent) => void;
24
+ /**
25
+ * Render the card root as an `<a>` styled identically, instead of a `<div>`. Use
26
+ * for card-styled links/navigation. When set, the root is natively focusable and
27
+ * Enter-activates, so the `role="button"`/`tabindex`/keydown shim used for a
28
+ * clickable `<div>` (see `onclick`) is not applied. `onclick` (if also provided)
29
+ * still fires alongside navigation. Omitted by default, so existing consumers are
30
+ * unaffected.
31
+ */
32
+ href?: string;
33
+ /** Anchor target (only applied when `href` is set), e.g. `_blank`. */
34
+ target?: string;
35
+ /** Anchor rel (only applied when `href` is set). Defaults to `noopener noreferrer` when `target="_blank"`. */
36
+ rel?: string;
21
37
  /**
22
38
  * Snippet rendered at the top-right of the header row, alongside title/description.
23
39
  * When omitted the header row is unchanged (title block takes full width).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/svelte-ui-components",
3
- "version": "2.118.1",
3
+ "version": "2.119.0",
4
4
  "description": "A themeable Svelte 5 UI component library with CSS custom property driven styling",
5
5
  "keywords": [
6
6
  "svelte",