noph-ui 0.22.5 → 0.23.1

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.
@@ -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) {
@@ -72,6 +53,11 @@
72
53
  onkeydown(event)
73
54
  }
74
55
  }
56
+ const setCheckInitialState = (el: HTMLElement) => {
57
+ if (parentElement?.getAttribute('data-value') === value) {
58
+ selected = true
59
+ }
60
+ }
75
61
  </script>
76
62
 
77
63
  {#snippet content()}
@@ -116,15 +102,18 @@
116
102
 
117
103
  {#if href}
118
104
  <a
119
- {@attach setActiveTab}
105
+ {@attach setCheckInitialState}
120
106
  {...attributes}
121
- tabindex={isActive ? 0 : -1}
107
+ {id}
108
+ tabindex={selected ? 0 : -1}
122
109
  role="tab"
110
+ aria-selected={selected}
111
+ data-value={value}
123
112
  bind:this={element}
124
113
  {href}
125
114
  class={[
126
115
  'np-tab',
127
- isActive && 'np-tab-content-active',
116
+ selected && 'np-tab-content-active',
128
117
  variant === 'primary' ? 'primary' : 'secondary',
129
118
  attributes.class,
130
119
  ]}
@@ -135,14 +124,17 @@
135
124
  </a>
136
125
  {:else}
137
126
  <div
138
- {@attach setActiveTab}
127
+ {@attach setCheckInitialState}
139
128
  {...attributes}
140
- tabindex={isActive ? 0 : -1}
129
+ {id}
130
+ tabindex={selected ? 0 : -1}
141
131
  role="tab"
132
+ aria-selected={selected}
133
+ data-value={value}
142
134
  bind:this={element}
143
135
  class={[
144
136
  'np-tab',
145
- isActive && 'np-tab-content-active',
137
+ selected && 'np-tab-content-active',
146
138
  variant === 'primary' ? 'primary' : 'secondary',
147
139
  attributes.class,
148
140
  ]}
@@ -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,32 +1,93 @@
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: HTMLElement[] | undefined = $state()
8
+ let activeTab: HTMLElement | undefined = $state()
9
+ const initialValue = value
12
10
 
13
11
  $effect(() => {
14
- value = active.value
12
+ if (value) {
13
+ const oldTab = activeTab
14
+ const newTab = tabs?.find((tab) => tab.getAttribute('data-value') === value)
15
+ if (newTab && oldTab && newTab !== oldTab) {
16
+ selectTab(newTab, oldTab, { id: newTab.id, value })
17
+ }
18
+ }
15
19
  })
16
- $effect(() => {
17
- active.value = value
20
+
21
+ const selectTab = (
22
+ newTab: HTMLElement,
23
+ oldTab: HTMLElement,
24
+ detail: { id: string; value: string | number },
25
+ ) => {
26
+ const oldIndicator = oldTab.querySelector('.np-indicator') as HTMLElement
27
+ const oldIndicatorRect = oldIndicator.getBoundingClientRect()
28
+ if (oldIndicatorRect) {
29
+ const newIndicator = newTab.querySelector<HTMLElement>('.np-indicator')
30
+ if (newIndicator) {
31
+ newIndicator.style.setProperty(
32
+ '--np-tab-indicator-start',
33
+ `${oldIndicatorRect.x - newIndicator.getBoundingClientRect().x}px`,
34
+ )
35
+ newIndicator.style.setProperty(
36
+ '--np-tab-indicator-scale',
37
+ `${oldIndicatorRect.width / newIndicator.clientWidth}`,
38
+ )
39
+ }
40
+ }
41
+ value = detail.value
42
+ activeTab = newTab
43
+ tabs?.forEach((tab) => {
44
+ tab.dispatchEvent(new CustomEvent('change', { detail }))
45
+ })
46
+ }
47
+
48
+ const onChange = (event: Event) => {
49
+ const { detail } = event as CustomEvent<{ id: string; value: string | number }>
50
+ const oldTab = activeTab
51
+ const newTab = tabs?.find((tab) => tab.id === detail.id)
52
+ if (newTab && oldTab && newTab !== oldTab) {
53
+ selectTab(newTab, oldTab, detail)
54
+ }
55
+ }
56
+
57
+ onMount(() => {
58
+ element?.addEventListener('change', onChange)
59
+ return () => {
60
+ element?.removeEventListener('change', onChange)
61
+ }
18
62
  })
63
+ const initialSetup = (el: HTMLElement) => {
64
+ if (activeTab === undefined) {
65
+ const childTabs = Array.from(el.querySelectorAll<HTMLElement>('.np-tab'))
66
+ activeTab =
67
+ childTabs && childTabs.length > 0
68
+ ? (childTabs.find((t) => {
69
+ return t.getAttribute('data-value') === initialValue
70
+ }) ?? childTabs[0])
71
+ : undefined
72
+ if (initialValue === undefined) {
73
+ value = activeTab?.getAttribute('data-value') ?? undefined
74
+ } else {
75
+ value = initialValue
76
+ }
77
+ tabs = childTabs
78
+ }
79
+ }
19
80
  </script>
20
81
 
21
- <div {...attributes} class={[attributes.class]}>
82
+ <div {@attach initialSetup} {...attributes} class={[attributes.class]}>
22
83
  <div
23
84
  class={['np-tabs']}
24
85
  role="tablist"
86
+ data-value={value}
25
87
  tabindex="-1"
26
88
  bind:this={element}
27
89
  onkeydown={(event) => {
28
- if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {
29
- const tabs: HTMLElement[] = Array.from(event.currentTarget.querySelectorAll('.np-tab'))
90
+ if (tabs && tabs.length > 0 && (event.key === 'ArrowRight' || event.key === 'ArrowLeft')) {
30
91
  const focusedTab = event.currentTarget.querySelector('.np-tab:focus') as HTMLElement
31
92
  const currentIndex = tabs.indexOf(focusedTab)
32
93
  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.5",
3
+ "version": "0.23.1",
4
4
  "license": "MIT",
5
5
  "homepage": "https://noph.dev",
6
6
  "repository": {