@14ch/svelte-ui 0.0.30 → 0.0.32

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.
@@ -398,7 +398,6 @@
398
398
  /* Nav: sub-menu */
399
399
  --svelte-ui-nav-sub-popup-min-width: 160px;
400
400
  --svelte-ui-nav-sub-popup-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
401
- --svelte-ui-nav-sub-bar-min-height: 48px;
402
401
  --svelte-ui-nav-item-child-indent: 32px;
403
402
  --svelte-ui-nav-bottom-sheet-overlay-bg: rgba(0, 0, 0, 0.4);
404
403
  --svelte-ui-nav-bottom-sheet-border-radius: 16px 16px 0 0;
@@ -2,7 +2,7 @@
2
2
 
3
3
  <script lang="ts">
4
4
  import NavItem from './NavItem.svelte';
5
- import type { NavItemSelectedStyle } from './NavItem.svelte';
5
+ import type { NavItemSelectedVariant } from './NavItem.svelte';
6
6
  import type { MenuItem } from '../types/menuItem';
7
7
  import type { NavVariant, ChildrenVariant } from '../types/propOptions';
8
8
  import { subscribeUrlChange } from '../utils/urlChange';
@@ -37,12 +37,20 @@
37
37
 
38
38
  // スタイル/レイアウト
39
39
  /** Visual style for the selected item. */
40
- selectedStyle?: NavItemSelectedStyle;
40
+ selectedVariant?: NavItemSelectedVariant;
41
41
  gap?: number | string;
42
42
  /** How child items are displayed. Defaults to `accordion` (vertical), `bar` (horizontal), `bottom-sheet` (mobile). */
43
43
  childrenVariant?: ChildrenVariant;
44
44
  /** Show chevron icon on parent items. @default true */
45
45
  chevron?: boolean;
46
+ /** Inline style applied to the nav container element. */
47
+ customContainerStyle?: string;
48
+ /** Inline style applied to each nav item element. */
49
+ customItemStyle?: string;
50
+ /** Inline style applied to each child nav item element. */
51
+ customChildrenItemStyle?: string;
52
+ /** Inline style applied to the children container (accordion/expanded list, bar row). */
53
+ customChildrenContainerStyle?: string;
46
54
 
47
55
  // ARIA/アクセシビリティ
48
56
  ariaLabel?: string;
@@ -68,10 +76,14 @@
68
76
  iconVariant = 'outlined',
69
77
 
70
78
  // スタイル/レイアウト
71
- selectedStyle,
79
+ selectedVariant,
72
80
  gap,
73
81
  childrenVariant = variant === 'mobile' ? 'bottom-sheet' : variant === 'vertical' ? 'accordion' : 'bar',
74
82
  chevron,
83
+ customContainerStyle,
84
+ customItemStyle,
85
+ customChildrenItemStyle,
86
+ customChildrenContainerStyle,
75
87
 
76
88
  // ARIA/アクセシビリティ
77
89
  ariaLabel,
@@ -253,6 +265,7 @@
253
265
  aria-label={ariaLabelledby ? undefined : ariaLabel}
254
266
  aria-labelledby={ariaLabelledby}
255
267
  style:--internal-nav-gap={gap != null ? (typeof gap === 'number' ? `${gap}px` : gap) : undefined}
268
+ style={customContainerStyle}
256
269
  {id}
257
270
  data-testid="nav"
258
271
  bind:this={navEl}
@@ -270,12 +283,15 @@
270
283
  {iconGrade}
271
284
  {iconOpticalSize}
272
285
  {iconVariant}
273
- {selectedStyle}
286
+ {selectedVariant}
274
287
  {childrenVariant}
275
288
  {chevron}
289
+ customStyle={customItemStyle}
290
+ customChildrenStyle={customChildrenItemStyle}
291
+ {customChildrenContainerStyle}
276
292
  {resolvedCurrentPath}
277
293
  {customPathMatcher}
278
- isChildrenExpanded={(childrenVariant === 'bar' || childrenVariant === 'accordion') && expandedParent === item}
294
+ isChildrenVisible={(childrenVariant === 'bar' || childrenVariant === 'accordion') && expandedParent === item}
279
295
  onChildrenToggle={handleToggle}
280
296
  onChildrenClose={() => { expandedParent = null; }}
281
297
  />
@@ -285,7 +301,7 @@
285
301
 
286
302
  <!-- bar モード: 選択中の親の子アイテムを横バーとして表示 -->
287
303
  {#if showSubBar && expandedParent?.children}
288
- <div class="nav__sub-bar" role="menu" tabindex="-1" onkeydown={handleSubBarKeyDown} bind:this={subBarEl}>
304
+ <div class="nav__children-bar" role="menu" tabindex="-1" style={customChildrenContainerStyle} onkeydown={handleSubBarKeyDown} bind:this={subBarEl}>
289
305
  {#each expandedParent.children as child}
290
306
  <NavItem
291
307
  item={child}
@@ -296,7 +312,8 @@
296
312
  {iconGrade}
297
313
  {iconOpticalSize}
298
314
  {iconVariant}
299
- {selectedStyle}
315
+ {selectedVariant}
316
+ customStyle={customChildrenItemStyle}
300
317
  isChild={true}
301
318
  {resolvedCurrentPath}
302
319
  {customPathMatcher}
@@ -340,10 +357,9 @@
340
357
  display: none;
341
358
  }
342
359
 
343
- .nav__sub-bar {
360
+ .nav__children-bar {
344
361
  display: flex;
345
362
  flex-direction: row;
346
363
  gap: var(--internal-nav-gap, var(--svelte-ui-nav-horizontal-item-gap));
347
364
  align-items: center;
348
- min-height: var(--svelte-ui-nav-sub-bar-min-height);
349
365
  }</style>
@@ -1,4 +1,4 @@
1
- import type { NavItemSelectedStyle } from './NavItem.svelte';
1
+ import type { NavItemSelectedVariant } from './NavItem.svelte';
2
2
  import type { MenuItem } from '../types/menuItem';
3
3
  import type { NavVariant, ChildrenVariant } from '../types/propOptions';
4
4
  import type { IconVariant, IconWeight, IconGrade, IconOpticalSize } from '../types/icon';
@@ -20,12 +20,20 @@ export type NavProps = {
20
20
  iconOpticalSize?: IconOpticalSize;
21
21
  iconVariant?: IconVariant;
22
22
  /** Visual style for the selected item. */
23
- selectedStyle?: NavItemSelectedStyle;
23
+ selectedVariant?: NavItemSelectedVariant;
24
24
  gap?: number | string;
25
25
  /** How child items are displayed. Defaults to `accordion` (vertical), `bar` (horizontal), `bottom-sheet` (mobile). */
26
26
  childrenVariant?: ChildrenVariant;
27
27
  /** Show chevron icon on parent items. @default true */
28
28
  chevron?: boolean;
29
+ /** Inline style applied to the nav container element. */
30
+ customContainerStyle?: string;
31
+ /** Inline style applied to each nav item element. */
32
+ customItemStyle?: string;
33
+ /** Inline style applied to each child nav item element. */
34
+ customChildrenItemStyle?: string;
35
+ /** Inline style applied to the children container (accordion/expanded list, bar row). */
36
+ customChildrenContainerStyle?: string;
29
37
  ariaLabel?: string;
30
38
  ariaLabelledby?: string;
31
39
  };
@@ -15,7 +15,7 @@
15
15
  // =========================================================================
16
16
  // Props, States & Constants
17
17
  // =========================================================================
18
- export type NavItemSelectedStyle = 'color' | 'filled' | 'tonal' | 'underline';
18
+ export type NavItemSelectedVariant = 'color' | 'filled' | 'tonal' | 'underline';
19
19
 
20
20
  export type NavItemProps = {
21
21
  // 基本プロパティ
@@ -37,17 +37,23 @@
37
37
  // スタイル/レイアウト
38
38
  /** Show chevron icon on parent items. @default true */
39
39
  chevron?: boolean;
40
- selectedStyle?: NavItemSelectedStyle;
40
+ selectedVariant?: NavItemSelectedVariant;
41
41
  /** How child items are displayed. Defaults to `accordion` (vertical), `bar` (horizontal), `bottom-sheet` (mobile). */
42
42
  childrenVariant?: ChildrenVariant;
43
+ /** Inline style applied to this nav item element. */
44
+ customStyle?: string;
45
+ /** Inline style applied to each child nav item element. */
46
+ customChildrenStyle?: string;
47
+ /** Inline style applied to the children container (accordion/expanded list). */
48
+ customChildrenContainerStyle?: string;
43
49
 
44
50
  // 状態/動作
45
51
  isSelected?: boolean;
46
52
  isDisabled?: boolean;
47
53
  /** When true, this item does not render its own children (prevents infinite recursion). */
48
54
  isChild?: boolean;
49
- /** For bar/accordion mode: whether this item's sub-menu is currently expanded. */
50
- isChildrenExpanded?: boolean;
55
+ /** Whether this item's children are currently visible. */
56
+ isChildrenVisible?: boolean;
51
57
 
52
58
  // イベントハンドラ
53
59
  /** For bar/accordion mode: called when this parent is clicked. */
@@ -73,14 +79,17 @@
73
79
 
74
80
  // スタイル/レイアウト
75
81
  chevron = true,
76
- selectedStyle,
82
+ selectedVariant,
77
83
  childrenVariant = variant === 'mobile' ? 'bottom-sheet' : variant === 'vertical' ? 'accordion' : 'bar',
84
+ customStyle,
85
+ customChildrenStyle,
86
+ customChildrenContainerStyle,
78
87
 
79
88
  // 状態/動作
80
89
  isSelected = false,
81
90
  isDisabled = false,
82
91
  isChild = false,
83
- isChildrenExpanded = false,
92
+ isChildrenVisible = $bindable(false),
84
93
 
85
94
  // イベントハンドラ
86
95
  onChildrenToggle,
@@ -99,7 +108,7 @@
99
108
  const hrefWithPrefix = $derived(item.href ? withPrefix(item.href) : undefined);
100
109
 
101
110
  const resolvedSelectedStyle = $derived(
102
- selectedStyle ?? (variant === 'vertical' || variant === 'horizontal' ? 'tonal' : 'color')
111
+ selectedVariant ?? (variant === 'vertical' || variant === 'horizontal' ? 'tonal' : 'color')
103
112
  );
104
113
 
105
114
  // isChild=true のアイテムでは children を展開しない(無限再帰防止)
@@ -121,36 +130,22 @@
121
130
  // =========================================================================
122
131
  // States
123
132
  // =========================================================================
124
- let isChildrenOpen = $state(false);
125
133
  let anchorEl: HTMLElement | undefined = $state();
126
134
  let popupMenuRef: PopupMenu | undefined = $state();
127
- let isPopupOpen = $state(false);
128
135
  let bottomSheetEl: HTMLElement | undefined = $state();
129
136
 
130
137
  const focusFirstChild = (container: HTMLElement | undefined) =>
131
138
  container?.querySelector<HTMLElement>('[data-nav-item-child]:not([tabindex="-1"])')?.focus();
132
139
 
133
140
  $effect(() => {
134
- if (isChildrenOpen) tick().then(() => focusFirstChild(bottomSheetEl));
141
+ if (isChildrenVisible && childrenVariant === 'bottom-sheet') tick().then(() => focusFirstChild(bottomSheetEl));
135
142
  });
136
143
 
137
144
  const popupPosition = $derived<PopupPosition>(
138
145
  variant === 'vertical' ? 'right-top' : variant === 'mobile' ? 'top-center' : 'bottom-left'
139
146
  );
140
147
 
141
- const isChildrenVisible = $derived(
142
- !hasChildren
143
- ? false
144
- : childrenVariant === 'expanded'
145
- ? true
146
- : childrenVariant === 'bottom-sheet'
147
- ? isChildrenOpen
148
- : childrenVariant === 'popup'
149
- ? isPopupOpen
150
- : isChildrenExpanded
151
- );
152
-
153
- const showChevron = $derived(hasChildren && chevron && variant !== 'mobile');
148
+ const showChevron = $derived(hasChildren && chevron && variant !== 'mobile' && childrenVariant !== 'expanded');
154
149
 
155
150
  // popup の方向に合わせたアイコン。それ以外は expand_more
156
151
  const chevronIcon = $derived(
@@ -168,16 +163,16 @@
168
163
  // Methods
169
164
  // =========================================================================
170
165
  const toggleOpen = () => {
171
- isChildrenOpen = !isChildrenOpen;
166
+ isChildrenVisible = !isChildrenVisible;
172
167
  };
173
168
 
174
169
  const closeOpen = () => {
175
- isChildrenOpen = false;
170
+ isChildrenVisible = false;
176
171
  };
177
172
 
178
173
  // popup の ArrowRight(vertical) / ArrowDown(horizontal) でサブメニューを開く
179
174
  const handleTriggerKeyDown = (event: KeyboardEvent) => {
180
- if (!hasChildren || childrenVariant !== 'popup' || isPopupOpen) return;
175
+ if (!hasChildren || childrenVariant !== 'popup' || isChildrenVisible) return;
181
176
  const openKey = variant === 'vertical' ? 'ArrowRight' : 'ArrowDown';
182
177
  if (event.key !== openKey) return;
183
178
  event.preventDefault();
@@ -227,7 +222,7 @@
227
222
  popupMenuRef?.toggle();
228
223
  } else if (childrenVariant === 'bottom-sheet') {
229
224
  toggleOpen();
230
- } else {
225
+ } else if (childrenVariant !== 'expanded') {
231
226
  onChildrenToggle?.(item);
232
227
  }
233
228
  };
@@ -246,6 +241,7 @@
246
241
  class:nav-item--style-filled={isSelected && resolvedSelectedStyle === 'filled'}
247
242
  class:nav-item--style-tonal={isSelected && resolvedSelectedStyle === 'tonal'}
248
243
  class:nav-item--style-underline={resolvedSelectedStyle === 'underline'}
244
+ style={customStyle}
249
245
  aria-disabled="true"
250
246
  tabindex="-1"
251
247
  data-nav-item={!isChild ? '' : undefined}
@@ -273,7 +269,7 @@
273
269
  =================================================================== -->
274
270
  <div
275
271
  class="nav-item__group nav-item__group--{variant}"
276
- class:nav-item__group--open={isChildrenVisible}
272
+ class:nav-item__group--open={childrenVariant === 'expanded' || isChildrenVisible}
277
273
  >
278
274
  <a
279
275
  href={resolvedParentHref}
@@ -284,8 +280,9 @@
284
280
  class:nav-item--style-filled={isSelected && resolvedSelectedStyle === 'filled'}
285
281
  class:nav-item--style-tonal={isSelected && resolvedSelectedStyle === 'tonal'}
286
282
  class:nav-item--style-underline={resolvedSelectedStyle === 'underline'}
283
+ style={customStyle}
287
284
  aria-current={isSelected ? 'page' : undefined}
288
- aria-expanded={childrenVariant === 'popup' ? isPopupOpen : isChildrenExpanded}
285
+ aria-expanded={childrenVariant === 'expanded' || isChildrenVisible}
289
286
  tabindex={0}
290
287
  data-nav-item
291
288
  data-testid="nav-item"
@@ -325,7 +322,7 @@
325
322
  {#if childrenVariant === 'popup'}
326
323
  <PopupMenu
327
324
  bind:this={popupMenuRef}
328
- bind:isOpen={isPopupOpen}
325
+ bind:isOpen={isChildrenVisible}
329
326
  anchorElement={anchorEl}
330
327
  position={popupPosition}
331
328
  menuItems={item.children!}
@@ -339,8 +336,8 @@
339
336
  {/if}
340
337
 
341
338
  <!-- accordion / expanded サブメニュー -->
342
- {#if (childrenVariant === 'accordion' || childrenVariant === 'expanded') && isChildrenVisible}
343
- <div class="nav-item__children" role="presentation" transition:slide={{ duration: 200 }}>
339
+ {#if childrenVariant === 'expanded' || (childrenVariant === 'accordion' && isChildrenVisible)}
340
+ <div class="nav-item__children" role="presentation" style={customChildrenContainerStyle} transition:slide={{ duration: 200 }}>
344
341
  {#each item.children! as child}
345
342
  <NavItem
346
343
  item={child}
@@ -351,7 +348,8 @@
351
348
  {iconGrade}
352
349
  {iconOpticalSize}
353
350
  {iconVariant}
354
- {selectedStyle}
351
+ {selectedVariant}
352
+ customStyle={customChildrenStyle}
355
353
  isChild={true}
356
354
  {resolvedCurrentPath}
357
355
  {customPathMatcher}
@@ -363,7 +361,7 @@
363
361
  {/if}
364
362
 
365
363
  <!-- bottom-sheet オーバーレイ -->
366
- {#if childrenVariant === 'bottom-sheet' && isChildrenOpen}
364
+ {#if childrenVariant === 'bottom-sheet' && isChildrenVisible}
367
365
  <div
368
366
  class="nav-item__bottom-sheet-backdrop"
369
367
  role="presentation"
@@ -388,7 +386,8 @@
388
386
  {iconGrade}
389
387
  {iconOpticalSize}
390
388
  {iconVariant}
391
- {selectedStyle}
389
+ {selectedVariant}
390
+ customStyle={customChildrenStyle}
392
391
  isChild={true}
393
392
  {resolvedCurrentPath}
394
393
  {customPathMatcher}
@@ -413,6 +412,7 @@
413
412
  class:nav-item--style-filled={isSelected && resolvedSelectedStyle === 'filled'}
414
413
  class:nav-item--style-tonal={isSelected && resolvedSelectedStyle === 'tonal'}
415
414
  class:nav-item--style-underline={resolvedSelectedStyle === 'underline'}
415
+ style={customStyle}
416
416
  aria-current={isSelected ? 'page' : undefined}
417
417
  tabindex={0}
418
418
  data-nav-item={!isChild ? '' : undefined}
@@ -2,7 +2,7 @@ import NavItem from './NavItem.svelte';
2
2
  import type { MenuItem } from '../types/menuItem';
3
3
  import type { NavVariant, ChildrenVariant } from '../types/propOptions';
4
4
  import type { IconVariant, IconWeight, IconGrade, IconOpticalSize } from '../types/icon';
5
- export type NavItemSelectedStyle = 'color' | 'filled' | 'tonal' | 'underline';
5
+ export type NavItemSelectedVariant = 'color' | 'filled' | 'tonal' | 'underline';
6
6
  export type NavItemProps = {
7
7
  item: MenuItem;
8
8
  variant?: NavVariant;
@@ -18,20 +18,26 @@ export type NavItemProps = {
18
18
  iconVariant?: IconVariant;
19
19
  /** Show chevron icon on parent items. @default true */
20
20
  chevron?: boolean;
21
- selectedStyle?: NavItemSelectedStyle;
21
+ selectedVariant?: NavItemSelectedVariant;
22
22
  /** How child items are displayed. Defaults to `accordion` (vertical), `bar` (horizontal), `bottom-sheet` (mobile). */
23
23
  childrenVariant?: ChildrenVariant;
24
+ /** Inline style applied to this nav item element. */
25
+ customStyle?: string;
26
+ /** Inline style applied to each child nav item element. */
27
+ customChildrenStyle?: string;
28
+ /** Inline style applied to the children container (accordion/expanded list). */
29
+ customChildrenContainerStyle?: string;
24
30
  isSelected?: boolean;
25
31
  isDisabled?: boolean;
26
32
  /** When true, this item does not render its own children (prevents infinite recursion). */
27
33
  isChild?: boolean;
28
- /** For bar/accordion mode: whether this item's sub-menu is currently expanded. */
29
- isChildrenExpanded?: boolean;
34
+ /** Whether this item's children are currently visible. */
35
+ isChildrenVisible?: boolean;
30
36
  /** For bar/accordion mode: called when this parent is clicked. */
31
37
  onChildrenToggle?: (item: MenuItem) => void;
32
38
  /** Called when a leaf item (no children) is clicked — used to close any open sub-menu. */
33
39
  onChildrenClose?: () => void;
34
40
  };
35
- declare const NavItem: import("svelte").Component<NavItemProps, {}, "">;
41
+ declare const NavItem: import("svelte").Component<NavItemProps, {}, "isChildrenVisible">;
36
42
  type NavItem = ReturnType<typeof NavItem>;
37
43
  export default NavItem;
@@ -80,7 +80,7 @@
80
80
  <Nav
81
81
  navItems={tabItems}
82
82
  variant="horizontal"
83
- selectedStyle="underline"
83
+ selectedVariant="underline"
84
84
  {pathPrefix}
85
85
  {customPathMatcher}
86
86
  {currentPath}
package/dist/index.d.ts CHANGED
@@ -75,7 +75,7 @@ export type { SkeletonHeadingProps } from './components/skeleton/SkeletonHeading
75
75
  export type { SkeletonMediaProps } from './components/skeleton/SkeletonMedia.svelte';
76
76
  export type { SwitchProps } from './components/Switch.svelte';
77
77
  export type { NavProps } from './components/Nav.svelte';
78
- export type { NavItemProps, NavItemSelectedStyle } from './components/NavItem.svelte';
78
+ export type { NavItemProps, NavItemSelectedVariant } from './components/NavItem.svelte';
79
79
  export type { TabProps } from './components/Tab.svelte';
80
80
  export type { TextareaProps } from './components/Textarea.svelte';
81
81
  export type { PopupPosition, SnackbarPosition, FabPosition, ButtonVariant, ButtonSize, SnackbarType, SnackbarVariant, BadgeVariant, DatepickerMode, FocusStyle, NavVariant, ChildrenVariant } from './types/propOptions';
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.30",
5
+ "version": "0.0.32",
6
6
  "type": "module",
7
7
  "keywords": [
8
8
  "svelte",