noph-ui 0.22.4 → 0.23.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,9 +8,6 @@
8
8
  label === undefined ? 'np-badge-container-no-label' : 'np-badge-container-label',
9
9
  ]}
10
10
  >
11
- {#if label === 0}
12
- <div class="np-badge-label">0</div>
13
- {/if}
14
11
  {#if label !== undefined}
15
12
  <div class="np-badge-label">
16
13
  {label}
@@ -20,7 +17,7 @@
20
17
 
21
18
  <style>
22
19
  .np-badge-container {
23
- display: flex;
20
+ display: inline-flex;
24
21
  justify-content: center;
25
22
  background-color: var(--np-color-error);
26
23
  border-radius: var(--np-shape-corner-full);
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import Ripple from '../ripple/Ripple.svelte'
3
- import { getContext } from 'svelte'
3
+ import { onMount } from 'svelte'
4
4
  import type { TabProps } from './types.ts'
5
5
  import Badge from '../badge/Badge.svelte'
6
6
 
@@ -15,49 +15,30 @@
15
15
  variant = 'primary',
16
16
  badge = false,
17
17
  badgeLabel,
18
+ selected = $bindable(false),
18
19
  ...attributes
19
20
  }: TabProps = $props()
20
- let activeTab: { value: string | number; node: HTMLElement } = getContext('activeTab')
21
21
  let element: HTMLElement | undefined = $state()
22
- let isActive = $state(activeTab.value === value)
22
+ let id = $props.id()
23
+ let parentElement = $derived(element?.parentElement)
23
24
 
24
- $effect(() => {
25
- if (activeTab.value === value) {
26
- if (element && element !== activeTab.node) {
27
- setTabActive(element)
28
- }
29
- isActive = true
30
- } else {
31
- isActive = false
25
+ const onChange = (event: Event) => {
26
+ const { detail } = event as CustomEvent<{ id: string }>
27
+ selected = detail.id === id
28
+ }
29
+
30
+ onMount(() => {
31
+ element?.addEventListener('change', onChange)
32
+ return () => {
33
+ element?.removeEventListener('change', onChange)
32
34
  }
33
35
  })
34
36
 
35
37
  const setTabActive = (el: HTMLElement) => {
36
- const oldTab = activeTab.node as HTMLElement | undefined
37
- const oldIndicator = oldTab?.querySelector('.np-indicator') as HTMLElement
38
- const oldIndicatorRect = oldIndicator?.getBoundingClientRect()
39
- if (oldIndicatorRect) {
40
- const newIndicator = el.querySelector<HTMLElement>('.np-indicator')
41
- if (newIndicator) {
42
- newIndicator.style.setProperty(
43
- '--np-tab-indicator-start',
44
- `${oldIndicatorRect.x - newIndicator.getBoundingClientRect().x}px`,
45
- )
46
- newIndicator.style.setProperty(
47
- '--np-tab-indicator-scale',
48
- `${oldIndicatorRect.width / newIndicator.clientWidth}`,
49
- )
50
- }
51
- }
52
- activeTab.value = value
53
- activeTab.node = el
38
+ parentElement?.dispatchEvent(new CustomEvent('change', { detail: { id: el.id, value } }))
39
+ selected = true
54
40
  }
55
41
 
56
- const setActiveTab = (el: HTMLElement) => {
57
- if (isActive) {
58
- activeTab.node = el
59
- }
60
- }
61
42
  const onClick = (event: MouseEvent & { currentTarget: EventTarget & HTMLElement }) => {
62
43
  setTabActive(event.currentTarget)
63
44
  if (onclick) {
@@ -116,15 +97,17 @@
116
97
 
117
98
  {#if href}
118
99
  <a
119
- {@attach setActiveTab}
120
100
  {...attributes}
121
- tabindex={isActive ? 0 : -1}
101
+ {id}
102
+ tabindex={selected ? 0 : -1}
122
103
  role="tab"
104
+ aria-selected={selected}
105
+ data-value={value}
123
106
  bind:this={element}
124
107
  {href}
125
108
  class={[
126
109
  'np-tab',
127
- isActive && 'np-tab-content-active',
110
+ selected && 'np-tab-content-active',
128
111
  variant === 'primary' ? 'primary' : 'secondary',
129
112
  attributes.class,
130
113
  ]}
@@ -135,14 +118,16 @@
135
118
  </a>
136
119
  {:else}
137
120
  <div
138
- {@attach setActiveTab}
139
121
  {...attributes}
140
- tabindex={isActive ? 0 : -1}
122
+ {id}
123
+ tabindex={selected ? 0 : -1}
141
124
  role="tab"
125
+ aria-selected={selected}
126
+ data-value={value}
142
127
  bind:this={element}
143
128
  class={[
144
129
  'np-tab',
145
- isActive && 'np-tab-content-active',
130
+ selected && 'np-tab-content-active',
146
131
  variant === 'primary' ? 'primary' : 'secondary',
147
132
  attributes.class,
148
133
  ]}
@@ -1,4 +1,4 @@
1
1
  import type { TabProps } from './types.ts';
2
- declare const Tab: import("svelte").Component<TabProps, {}, "">;
2
+ declare const Tab: import("svelte").Component<TabProps, {}, "selected">;
3
3
  type Tab = ReturnType<typeof Tab>;
4
4
  export default Tab;
@@ -1,20 +1,55 @@
1
1
  <script lang="ts">
2
2
  import Divider from '../divider/Divider.svelte'
3
- import { setContext } from 'svelte'
3
+ import { onMount } from 'svelte'
4
4
  import type { TabsProps } from './types.ts'
5
5
 
6
6
  let { children, element = $bindable(), value = $bindable(), ...attributes }: TabsProps = $props()
7
- let active = $state({
8
- value: value,
9
- node: element?.firstChild as HTMLElement | undefined,
10
- })
11
- setContext('activeTab', active)
7
+ let tabs = $derived(
8
+ element ? Array.from(element.querySelectorAll<HTMLElement>('.np-tab')) : undefined,
9
+ )
10
+ let activeTab = $derived(
11
+ tabs && tabs.length > 0
12
+ ? (tabs.find((t) => {
13
+ return t.getAttribute('data-value') === value
14
+ }) ?? tabs[0])
15
+ : undefined,
16
+ )
12
17
 
13
18
  $effect(() => {
14
- value = active.value
19
+ if (value === undefined) {
20
+ value = activeTab?.getAttribute('data-value') ?? undefined
21
+ }
15
22
  })
16
- $effect(() => {
17
- active.value = value
23
+
24
+ onMount(() => {
25
+ element?.addEventListener('change', (event) => {
26
+ const { detail } = event as CustomEvent<{ id: string; value: string | number }>
27
+ const oldTab = activeTab
28
+ const newTab = tabs?.find((tab) => tab.id === detail.id)
29
+ if (newTab && oldTab) {
30
+ const oldIndicator = oldTab.querySelector('.np-indicator') as HTMLElement
31
+ const oldIndicatorRect = oldIndicator.getBoundingClientRect()
32
+ if (oldIndicatorRect) {
33
+ const newIndicator = newTab.querySelector<HTMLElement>('.np-indicator')
34
+ if (newIndicator) {
35
+ newIndicator.style.setProperty(
36
+ '--np-tab-indicator-start',
37
+ `${oldIndicatorRect.x - newIndicator.getBoundingClientRect().x}px`,
38
+ )
39
+ newIndicator.style.setProperty(
40
+ '--np-tab-indicator-scale',
41
+ `${oldIndicatorRect.width / newIndicator.clientWidth}`,
42
+ )
43
+ }
44
+ }
45
+ value = detail.value
46
+ activeTab = newTab
47
+ tabs?.forEach((tab) => {
48
+ tab.dispatchEvent(new CustomEvent('change', { detail }))
49
+ })
50
+ }
51
+ })
52
+ activeTab?.dispatchEvent(new CustomEvent('change', { detail: { id: activeTab.id } }))
18
53
  })
19
54
  </script>
20
55
 
@@ -25,8 +60,7 @@
25
60
  tabindex="-1"
26
61
  bind:this={element}
27
62
  onkeydown={(event) => {
28
- if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {
29
- const tabs: HTMLElement[] = Array.from(event.currentTarget.querySelectorAll('.np-tab'))
63
+ if (tabs && tabs.length > 0 && (event.key === 'ArrowRight' || event.key === 'ArrowLeft')) {
30
64
  const focusedTab = event.currentTarget.querySelector('.np-tab:focus') as HTMLElement
31
65
  const currentIndex = tabs.indexOf(focusedTab)
32
66
  const index = currentIndex + (event.key === 'ArrowRight' ? 1 : -1)
@@ -8,8 +8,9 @@ export interface TabProps extends HTMLAttributes<HTMLElement> {
8
8
  icon?: Snippet;
9
9
  badge?: boolean;
10
10
  badgeLabel?: string | number;
11
+ selected?: boolean;
11
12
  }
12
13
  export interface TabsProps extends HTMLAttributes<HTMLDivElement> {
13
- value: string | number;
14
+ value?: string | number;
14
15
  element?: HTMLElement;
15
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noph-ui",
3
- "version": "0.22.4",
3
+ "version": "0.23.0",
4
4
  "license": "MIT",
5
5
  "homepage": "https://noph.dev",
6
6
  "repository": {