@14ch/svelte-ui 0.0.32 → 0.0.34

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.
@@ -395,6 +395,8 @@
395
395
  --svelte-ui-nav-vertical-item-gap: 8px;
396
396
  /* Nav: horizontal */
397
397
  --svelte-ui-nav-horizontal-item-gap: 8px;
398
+ /* Nav: children spacing (parent item → children list) */
399
+ --svelte-ui-nav-children-offset: 8px;
398
400
  /* Nav: sub-menu */
399
401
  --svelte-ui-nav-sub-popup-min-width: 160px;
400
402
  --svelte-ui-nav-sub-popup-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
@@ -78,7 +78,11 @@
78
78
  // スタイル/レイアウト
79
79
  selectedVariant,
80
80
  gap,
81
- childrenVariant = variant === 'mobile' ? 'bottom-sheet' : variant === 'vertical' ? 'accordion' : 'bar',
81
+ childrenVariant = variant === 'mobile'
82
+ ? 'bottom-sheet'
83
+ : variant === 'vertical'
84
+ ? 'accordion'
85
+ : 'bar',
82
86
  chevron,
83
87
  customContainerStyle,
84
88
  customItemStyle,
@@ -107,21 +111,24 @@
107
111
  $effect(() => {
108
112
  return subscribeUrlChange(() => {
109
113
  resolvedCurrentPath = getCurrentPath(currentPath);
110
- if (childrenVariant !== 'accordion') expandedParent = null;
111
114
  });
112
115
  });
113
116
 
114
- // accordion モード: アクティブな子を持つ親を自動展開
117
+ // accordion / bar モード: アクティブな親を自動展開
115
118
  $effect(() => {
116
- if (childrenVariant !== 'accordion' || !resolvedCurrentPath) return;
117
- const activeParent = navItems.find((item) =>
118
- item.children?.some(
119
- (child) =>
120
- child.href &&
121
- matchPath(resolvedCurrentPath, child.href, child, pathPrefix, customPathMatcher)
122
- )
119
+ if ((childrenVariant !== 'accordion' && childrenVariant !== 'bar') || !resolvedCurrentPath)
120
+ return;
121
+ const selected = navItems[selectedIndex];
122
+ const activeParent = navItems.find(
123
+ (item) =>
124
+ (item === selected && !!item.children?.length) ||
125
+ item.children?.some(
126
+ (child) =>
127
+ child.href &&
128
+ matchPath(resolvedCurrentPath, child.href, child, pathPrefix, customPathMatcher)
129
+ )
123
130
  );
124
- if (activeParent) expandedParent = activeParent;
131
+ expandedParent = activeParent ?? null;
125
132
  });
126
133
 
127
134
  // =========================================================================
@@ -140,7 +147,7 @@
140
147
  const selector = includeChildren ? '[data-nav-item], [data-nav-item-child]' : '[data-nav-item]';
141
148
  const navItemEls = Array.from(
142
149
  (event.currentTarget as HTMLElement).querySelectorAll<HTMLElement>(selector)
143
- ).filter(el => el.tabIndex !== -1);
150
+ ).filter((el) => el.tabIndex !== -1);
144
151
 
145
152
  if (navItemEls.length === 0) return;
146
153
 
@@ -223,29 +230,27 @@
223
230
  }
224
231
  };
225
232
 
226
- const handleToggle = (item: MenuItem) => {
227
- if (childrenVariant === 'accordion') {
228
- // accordion: 開くのみ(再クリックで閉じない、他は自動的に閉じる)
229
- expandedParent = item;
230
- } else {
231
- // bar: トグル
232
- expandedParent = expandedParent === item ? null : item;
233
- }
234
- };
235
-
236
233
  // =========================================================================
237
234
  // $derived
238
235
  // =========================================================================
239
236
  const selectedIndex = $derived.by(() => {
240
237
  for (let i = 0; i < navItems.length; i++) {
241
238
  const item = navItems[i];
242
- if (item.href && matchPath(resolvedCurrentPath, item.href, item, pathPrefix, customPathMatcher)) {
239
+ if (
240
+ item.href &&
241
+ matchPath(resolvedCurrentPath, item.href, item, pathPrefix, customPathMatcher)
242
+ ) {
243
243
  return i;
244
244
  }
245
245
  // bar モード: 子が選択されていれば親も選択とみなす
246
- if (childrenVariant === 'bar' && item.children?.some(
247
- (child) => child.href && matchPath(resolvedCurrentPath, child.href, child, pathPrefix, customPathMatcher)
248
- )) {
246
+ if (
247
+ childrenVariant === 'bar' &&
248
+ item.children?.some(
249
+ (child) =>
250
+ child.href &&
251
+ matchPath(resolvedCurrentPath, child.href, child, pathPrefix, customPathMatcher)
252
+ )
253
+ ) {
249
254
  return i;
250
255
  }
251
256
  }
@@ -257,7 +262,8 @@
257
262
  );
258
263
 
259
264
  const isChildSelected = (child: MenuItem) =>
260
- !!child.href && matchPath(resolvedCurrentPath, child.href, child, pathPrefix, customPathMatcher);
265
+ !!child.href &&
266
+ matchPath(resolvedCurrentPath, child.href, child, pathPrefix, customPathMatcher);
261
267
  </script>
262
268
 
263
269
  <nav
@@ -271,37 +277,43 @@
271
277
  bind:this={navEl}
272
278
  >
273
279
  <div style="display: contents" role="presentation" onkeydown={handleKeyDown}>
274
- {#each navItems as item, index}
275
- <NavItem
276
- {item}
277
- {variant}
278
- {pathPrefix}
279
- isSelected={index === selectedIndex}
280
- isDisabled={item.disabled ?? false}
281
- {iconFilled}
282
- {iconWeight}
283
- {iconGrade}
284
- {iconOpticalSize}
285
- {iconVariant}
286
- {selectedVariant}
287
- {childrenVariant}
288
- {chevron}
289
- customStyle={customItemStyle}
290
- customChildrenStyle={customChildrenItemStyle}
291
- {customChildrenContainerStyle}
292
- {resolvedCurrentPath}
293
- {customPathMatcher}
294
- isChildrenVisible={(childrenVariant === 'bar' || childrenVariant === 'accordion') && expandedParent === item}
295
- onChildrenToggle={handleToggle}
296
- onChildrenClose={() => { expandedParent = null; }}
297
- />
298
- {/each}
280
+ {#each navItems as item, index}
281
+ <NavItem
282
+ {item}
283
+ {variant}
284
+ {pathPrefix}
285
+ isSelected={index === selectedIndex}
286
+ isDisabled={item.disabled ?? false}
287
+ {iconFilled}
288
+ {iconWeight}
289
+ {iconGrade}
290
+ {iconOpticalSize}
291
+ {iconVariant}
292
+ {selectedVariant}
293
+ {childrenVariant}
294
+ {chevron}
295
+ customStyle={customItemStyle}
296
+ customChildrenStyle={customChildrenItemStyle}
297
+ {customChildrenContainerStyle}
298
+ {resolvedCurrentPath}
299
+ {customPathMatcher}
300
+ isChildrenVisible={(childrenVariant === 'bar' || childrenVariant === 'accordion') &&
301
+ expandedParent === item}
302
+ />
303
+ {/each}
299
304
  </div>
300
305
  </nav>
301
306
 
302
307
  <!-- bar モード: 選択中の親の子アイテムを横バーとして表示 -->
303
308
  {#if showSubBar && expandedParent?.children}
304
- <div class="nav__children-bar" role="menu" tabindex="-1" style={customChildrenContainerStyle} onkeydown={handleSubBarKeyDown} bind:this={subBarEl}>
309
+ <div
310
+ class="nav__children-bar"
311
+ role="menu"
312
+ tabindex="-1"
313
+ style={customChildrenContainerStyle}
314
+ onkeydown={handleSubBarKeyDown}
315
+ bind:this={subBarEl}
316
+ >
305
317
  {#each expandedParent.children as child}
306
318
  <NavItem
307
319
  item={child}
@@ -362,4 +374,5 @@
362
374
  flex-direction: row;
363
375
  gap: var(--internal-nav-gap, var(--svelte-ui-nav-horizontal-item-gap));
364
376
  align-items: center;
377
+ padding-top: var(--svelte-ui-nav-children-offset);
365
378
  }</style>
@@ -55,11 +55,6 @@
55
55
  /** Whether this item's children are currently visible. */
56
56
  isChildrenVisible?: boolean;
57
57
 
58
- // イベントハンドラ
59
- /** For bar/accordion mode: called when this parent is clicked. */
60
- onChildrenToggle?: (item: MenuItem) => void;
61
- /** Called when a leaf item (no children) is clicked — used to close any open sub-menu. */
62
- onChildrenClose?: () => void;
63
58
  };
64
59
 
65
60
  let {
@@ -91,9 +86,6 @@
91
86
  isChild = false,
92
87
  isChildrenVisible = $bindable(false),
93
88
 
94
- // イベントハンドラ
95
- onChildrenToggle,
96
- onChildrenClose
97
89
  }: NavItemProps = $props();
98
90
 
99
91
  // =========================================================================
@@ -222,8 +214,6 @@
222
214
  popupMenuRef?.toggle();
223
215
  } else if (childrenVariant === 'bottom-sheet') {
224
216
  toggleOpen();
225
- } else if (childrenVariant !== 'expanded') {
226
- onChildrenToggle?.(item);
227
217
  }
228
218
  };
229
219
 
@@ -373,6 +363,7 @@
373
363
  role="menu"
374
364
  tabindex="-1"
375
365
  transition:fly={{ y: 100, duration: 250 }}
366
+ onclick={closeOpen}
376
367
  onkeydown={(e) => handleChildKeyDown(e, true)}
377
368
  bind:this={bottomSheetEl}
378
369
  >
@@ -393,7 +384,6 @@
393
384
  {customPathMatcher}
394
385
  isSelected={isChildSelected(child)}
395
386
  isDisabled={child.disabled ?? false}
396
- onChildrenClose={closeOpen}
397
387
  />
398
388
  {/each}
399
389
  </div>
@@ -418,7 +408,6 @@
418
408
  data-nav-item={!isChild ? '' : undefined}
419
409
  data-nav-item-child={isChild ? '' : undefined}
420
410
  data-testid="nav-item"
421
- onclick={onChildrenClose}
422
411
  >
423
412
  {#if item.icon}
424
413
  <div class="nav-item__icon">
@@ -623,7 +612,7 @@
623
612
  .nav-item__children {
624
613
  display: flex;
625
614
  flex-direction: column;
626
- padding-top: var(--internal-nav-gap, var(--svelte-ui-nav-vertical-item-gap));
615
+ padding-top: var(--svelte-ui-nav-children-offset);
627
616
  gap: var(--internal-nav-gap, var(--svelte-ui-nav-vertical-item-gap));
628
617
  overflow: hidden;
629
618
  }
@@ -33,10 +33,6 @@ export type NavItemProps = {
33
33
  isChild?: boolean;
34
34
  /** Whether this item's children are currently visible. */
35
35
  isChildrenVisible?: boolean;
36
- /** For bar/accordion mode: called when this parent is clicked. */
37
- onChildrenToggle?: (item: MenuItem) => void;
38
- /** Called when a leaf item (no children) is clicked — used to close any open sub-menu. */
39
- onChildrenClose?: () => void;
40
36
  };
41
37
  declare const NavItem: import("svelte").Component<NavItemProps, {}, "isChildrenVisible">;
42
38
  type NavItem = ReturnType<typeof NavItem>;
@@ -36,6 +36,14 @@
36
36
  selectedTextColor?: string;
37
37
  /** Custom CSS color for the active tab indicator bar. */
38
38
  selectedBarColor?: string;
39
+ /** Inline style applied to the tab container element. */
40
+ customContainerStyle?: string;
41
+ /** Inline style applied to each tab item element. */
42
+ customItemStyle?: string;
43
+ /** Inline style applied to the children container. */
44
+ customChildrenContainerStyle?: string;
45
+ /** Inline style applied to each child item element. */
46
+ customChildrenItemStyle?: string;
39
47
 
40
48
  // ARIA/アクセシビリティ
41
49
  ariaLabel?: string;
@@ -63,6 +71,10 @@
63
71
  textColor,
64
72
  selectedTextColor,
65
73
  selectedBarColor,
74
+ customContainerStyle,
75
+ customItemStyle,
76
+ customChildrenContainerStyle,
77
+ customChildrenItemStyle,
66
78
 
67
79
  // ARIA/アクセシビリティ
68
80
  ariaLabel = 'Tabs',
@@ -90,6 +102,10 @@
90
102
  {iconGrade}
91
103
  {iconOpticalSize}
92
104
  {iconVariant}
105
+ {customContainerStyle}
106
+ {customItemStyle}
107
+ {customChildrenContainerStyle}
108
+ {customChildrenItemStyle}
93
109
  {ariaLabel}
94
110
  {ariaLabelledby}
95
111
  />
@@ -21,6 +21,14 @@ export type TabProps = {
21
21
  selectedTextColor?: string;
22
22
  /** Custom CSS color for the active tab indicator bar. */
23
23
  selectedBarColor?: string;
24
+ /** Inline style applied to the tab container element. */
25
+ customContainerStyle?: string;
26
+ /** Inline style applied to each tab item element. */
27
+ customItemStyle?: string;
28
+ /** Inline style applied to the children container. */
29
+ customChildrenContainerStyle?: string;
30
+ /** Inline style applied to each child item element. */
31
+ customChildrenItemStyle?: string;
24
32
  ariaLabel?: string;
25
33
  ariaLabelledby?: string;
26
34
  };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@14ch/svelte-ui",
3
3
  "description": "Modern Svelte UI components library with TypeScript support",
4
4
  "private": false,
5
- "version": "0.0.32",
5
+ "version": "0.0.34",
6
6
  "type": "module",
7
7
  "keywords": [
8
8
  "svelte",