@duskmoon-dev/el-bottom-navigation 0.4.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.
@@ -0,0 +1,329 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
6
+ var __toCommonJS = (from) => {
7
+ var entry = __moduleCache.get(from), desc;
8
+ if (entry)
9
+ return entry;
10
+ entry = __defProp({}, "__esModule", { value: true });
11
+ if (from && typeof from === "object" || typeof from === "function")
12
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
13
+ get: () => from[key],
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ }));
16
+ __moduleCache.set(from, entry);
17
+ return entry;
18
+ };
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, {
22
+ get: all[name],
23
+ enumerable: true,
24
+ configurable: true,
25
+ set: (newValue) => all[name] = () => newValue
26
+ });
27
+ };
28
+
29
+ // src/index.ts
30
+ var exports_src = {};
31
+ __export(exports_src, {
32
+ register: () => register,
33
+ ElDmBottomNavigation: () => ElDmBottomNavigation
34
+ });
35
+ module.exports = __toCommonJS(exports_src);
36
+
37
+ // src/el-dm-bottom-navigation.ts
38
+ var import_el_core = require("@duskmoon-dev/el-core");
39
+ var styles = import_el_core.css`
40
+ :host {
41
+ --bottom-nav-height: 56px;
42
+ --bottom-nav-bg: var(--color-surface, #ffffff);
43
+ --bottom-nav-border: var(--color-border, #e5e7eb);
44
+ --bottom-nav-text: var(--color-text-secondary, #6b7280);
45
+ --bottom-nav-text-active: var(--color-primary, #3b82f6);
46
+ --bottom-nav-icon-size: 24px;
47
+ --bottom-nav-label-size: 0.75rem;
48
+ --bottom-nav-shadow: 0 -1px 3px rgba(0, 0, 0, 0.1);
49
+
50
+ display: block;
51
+ position: fixed;
52
+ bottom: 0;
53
+ left: 0;
54
+ right: 0;
55
+ z-index: 1000;
56
+ }
57
+
58
+ :host([hidden]) {
59
+ display: none !important;
60
+ }
61
+
62
+ :host([position='static']) {
63
+ position: static;
64
+ }
65
+
66
+ :host([position='sticky']) {
67
+ position: sticky;
68
+ }
69
+
70
+ .bottom-nav {
71
+ display: flex;
72
+ align-items: center;
73
+ justify-content: space-around;
74
+ height: var(--bottom-nav-height);
75
+ background: var(--bottom-nav-bg);
76
+ border-top: 1px solid var(--bottom-nav-border);
77
+ box-shadow: var(--bottom-nav-shadow);
78
+ padding: 0;
79
+ margin: 0;
80
+ /* Safe area for iOS devices */
81
+ padding-bottom: env(safe-area-inset-bottom, 0);
82
+ }
83
+
84
+ .nav-item {
85
+ flex: 1;
86
+ display: flex;
87
+ flex-direction: column;
88
+ align-items: center;
89
+ justify-content: center;
90
+ gap: 2px;
91
+ padding: 6px 12px;
92
+ min-width: 0;
93
+ max-width: 168px;
94
+ height: 100%;
95
+ background: transparent;
96
+ border: none;
97
+ cursor: pointer;
98
+ color: var(--bottom-nav-text);
99
+ text-decoration: none;
100
+ transition:
101
+ color 0.2s ease,
102
+ transform 0.1s ease;
103
+ -webkit-tap-highlight-color: transparent;
104
+ user-select: none;
105
+ }
106
+
107
+ .nav-item:focus {
108
+ outline: none;
109
+ }
110
+
111
+ .nav-item:focus-visible {
112
+ outline: 2px solid var(--bottom-nav-text-active);
113
+ outline-offset: -2px;
114
+ border-radius: 4px;
115
+ }
116
+
117
+ .nav-item:active:not([disabled]) {
118
+ transform: scale(0.95);
119
+ }
120
+
121
+ .nav-item[aria-selected='true'],
122
+ .nav-item.active {
123
+ color: var(--bottom-nav-text-active);
124
+ }
125
+
126
+ .nav-item[disabled] {
127
+ opacity: 0.5;
128
+ cursor: not-allowed;
129
+ pointer-events: none;
130
+ }
131
+
132
+ .nav-icon {
133
+ display: flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ width: var(--bottom-nav-icon-size);
137
+ height: var(--bottom-nav-icon-size);
138
+ flex-shrink: 0;
139
+ }
140
+
141
+ .nav-icon ::slotted(*),
142
+ .nav-icon svg,
143
+ .nav-icon img {
144
+ width: 100%;
145
+ height: 100%;
146
+ }
147
+
148
+ .nav-label {
149
+ font-size: var(--bottom-nav-label-size);
150
+ font-weight: 500;
151
+ line-height: 1.2;
152
+ white-space: nowrap;
153
+ overflow: hidden;
154
+ text-overflow: ellipsis;
155
+ max-width: 100%;
156
+ }
157
+
158
+ /* Hide labels on very small screens */
159
+ @media (max-width: 320px) {
160
+ .nav-label {
161
+ display: none;
162
+ }
163
+
164
+ .nav-icon {
165
+ width: 28px;
166
+ height: 28px;
167
+ }
168
+ }
169
+
170
+ /* Color variants */
171
+ :host([color='secondary']) {
172
+ --bottom-nav-text-active: var(--color-secondary, #8b5cf6);
173
+ }
174
+
175
+ :host([color='success']) {
176
+ --bottom-nav-text-active: var(--color-success, #22c55e);
177
+ }
178
+
179
+ :host([color='warning']) {
180
+ --bottom-nav-text-active: var(--color-warning, #f59e0b);
181
+ }
182
+
183
+ :host([color='error']) {
184
+ --bottom-nav-text-active: var(--color-error, #ef4444);
185
+ }
186
+
187
+ /* Badge indicator for items */
188
+ .nav-item-badge {
189
+ position: absolute;
190
+ top: 4px;
191
+ right: calc(50% - 18px);
192
+ min-width: 8px;
193
+ height: 8px;
194
+ background: var(--color-error, #ef4444);
195
+ border-radius: 50%;
196
+ }
197
+
198
+ .nav-item-wrapper {
199
+ position: relative;
200
+ flex: 1;
201
+ display: flex;
202
+ justify-content: center;
203
+ max-width: 168px;
204
+ }
205
+
206
+ /* Slot for custom items */
207
+ ::slotted(el-dm-bottom-navigation-item) {
208
+ flex: 1;
209
+ }
210
+ `;
211
+
212
+ class ElDmBottomNavigation extends import_el_core.BaseElement {
213
+ static properties = {
214
+ items: { type: Array, reflect: false, default: [] },
215
+ value: { type: String, reflect: true },
216
+ color: { type: String, reflect: true, default: "primary" },
217
+ position: { type: String, reflect: true, default: "fixed" }
218
+ };
219
+ constructor() {
220
+ super();
221
+ this.attachStyles(styles);
222
+ }
223
+ connectedCallback() {
224
+ super.connectedCallback();
225
+ this.addEventListener("click", this._handleClick.bind(this));
226
+ this.addEventListener("keydown", this._handleKeydown.bind(this));
227
+ }
228
+ disconnectedCallback() {
229
+ super.disconnectedCallback();
230
+ this.removeEventListener("click", this._handleClick.bind(this));
231
+ this.removeEventListener("keydown", this._handleKeydown.bind(this));
232
+ }
233
+ _handleClick(event) {
234
+ const target = event.target;
235
+ const navItem = target.closest("[data-value]");
236
+ if (!navItem || navItem.hasAttribute("disabled")) {
237
+ return;
238
+ }
239
+ const value = navItem.dataset.value;
240
+ if (value && value !== this.value) {
241
+ const item = this.items.find((i) => i.value === value);
242
+ this.value = value;
243
+ this.emit("change", { value, item });
244
+ }
245
+ }
246
+ _handleKeydown(event) {
247
+ const target = event.target;
248
+ if (!target.classList.contains("nav-item"))
249
+ return;
250
+ const items = Array.from(this.shadowRoot.querySelectorAll(".nav-item:not([disabled])"));
251
+ const currentIndex = items.indexOf(target);
252
+ let nextIndex = -1;
253
+ switch (event.key) {
254
+ case "ArrowLeft":
255
+ case "ArrowUp":
256
+ event.preventDefault();
257
+ nextIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
258
+ break;
259
+ case "ArrowRight":
260
+ case "ArrowDown":
261
+ event.preventDefault();
262
+ nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
263
+ break;
264
+ case "Home":
265
+ event.preventDefault();
266
+ nextIndex = 0;
267
+ break;
268
+ case "End":
269
+ event.preventDefault();
270
+ nextIndex = items.length - 1;
271
+ break;
272
+ case "Enter":
273
+ case " ":
274
+ event.preventDefault();
275
+ target.click();
276
+ return;
277
+ }
278
+ if (nextIndex >= 0 && items[nextIndex]) {
279
+ items[nextIndex].focus();
280
+ }
281
+ }
282
+ _renderItem(item, index) {
283
+ const isSelected = item.value === this.value;
284
+ const Tag = item.href ? "a" : "button";
285
+ const hrefAttr = item.href ? `href="${item.href}"` : "";
286
+ const disabledAttr = item.disabled ? "disabled" : "";
287
+ const typeAttr = Tag === "button" ? 'type="button"' : "";
288
+ return `
289
+ <div class="nav-item-wrapper">
290
+ <${Tag}
291
+ class="nav-item"
292
+ part="item"
293
+ role="tab"
294
+ tabindex="${item.disabled ? "-1" : "0"}"
295
+ aria-selected="${isSelected}"
296
+ data-value="${item.value}"
297
+ ${hrefAttr}
298
+ ${disabledAttr}
299
+ ${typeAttr}
300
+ >
301
+ ${item.icon ? `
302
+ <span class="nav-icon" part="icon">
303
+ ${item.icon}
304
+ </span>
305
+ ` : ""}
306
+ <span class="nav-label" part="label">${item.label}</span>
307
+ </${Tag}>
308
+ </div>
309
+ `;
310
+ }
311
+ render() {
312
+ const hasItems = this.items && this.items.length > 0;
313
+ return `
314
+ <nav class="bottom-nav" part="container" role="tablist" aria-label="Bottom navigation">
315
+ ${hasItems ? this.items.map((item, i) => this._renderItem(item, i)).join("") : "<slot></slot>"}
316
+ </nav>
317
+ `;
318
+ }
319
+ }
320
+
321
+ // src/index.ts
322
+ function register() {
323
+ if (!customElements.get("el-dm-bottom-navigation")) {
324
+ customElements.define("el-dm-bottom-navigation", ElDmBottomNavigation);
325
+ }
326
+ }
327
+
328
+ //# debugId=F376226EE32C567964756E2164756E21
329
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/el-dm-bottom-navigation.ts", "../../src/index.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * DuskMoon Bottom Navigation Element\n *\n * A mobile-first bottom navigation bar component with nav items and icons.\n * Fixed position at the bottom of the viewport for easy thumb access.\n *\n * @element el-dm-bottom-navigation\n *\n * @attr {Array} items - Array of navigation items with { value, label, icon? } structure\n * @attr {string} value - Currently selected item value\n * @attr {string} color - Color theme: primary, secondary, or custom color\n *\n * @slot - Default slot for custom nav items (el-dm-bottom-navigation-item)\n *\n * @csspart container - The navigation container\n * @csspart item - Individual navigation item\n * @csspart icon - Item icon container\n * @csspart label - Item label\n *\n * @fires change - Fired when selection changes, detail: { value, item }\n */\n\nimport { BaseElement, css } from '@duskmoon-dev/el-core';\n\n/**\n * Navigation item structure\n */\nexport interface BottomNavigationItem {\n /** Unique identifier for the item */\n value: string;\n /** Display label */\n label: string;\n /** Icon HTML or SVG string */\n icon?: string;\n /** Whether the item is disabled */\n disabled?: boolean;\n /** Optional href for link behavior */\n href?: string;\n}\n\nconst styles = css`\n :host {\n --bottom-nav-height: 56px;\n --bottom-nav-bg: var(--color-surface, #ffffff);\n --bottom-nav-border: var(--color-border, #e5e7eb);\n --bottom-nav-text: var(--color-text-secondary, #6b7280);\n --bottom-nav-text-active: var(--color-primary, #3b82f6);\n --bottom-nav-icon-size: 24px;\n --bottom-nav-label-size: 0.75rem;\n --bottom-nav-shadow: 0 -1px 3px rgba(0, 0, 0, 0.1);\n\n display: block;\n position: fixed;\n bottom: 0;\n left: 0;\n right: 0;\n z-index: 1000;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n\n :host([position='static']) {\n position: static;\n }\n\n :host([position='sticky']) {\n position: sticky;\n }\n\n .bottom-nav {\n display: flex;\n align-items: center;\n justify-content: space-around;\n height: var(--bottom-nav-height);\n background: var(--bottom-nav-bg);\n border-top: 1px solid var(--bottom-nav-border);\n box-shadow: var(--bottom-nav-shadow);\n padding: 0;\n margin: 0;\n /* Safe area for iOS devices */\n padding-bottom: env(safe-area-inset-bottom, 0);\n }\n\n .nav-item {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 2px;\n padding: 6px 12px;\n min-width: 0;\n max-width: 168px;\n height: 100%;\n background: transparent;\n border: none;\n cursor: pointer;\n color: var(--bottom-nav-text);\n text-decoration: none;\n transition:\n color 0.2s ease,\n transform 0.1s ease;\n -webkit-tap-highlight-color: transparent;\n user-select: none;\n }\n\n .nav-item:focus {\n outline: none;\n }\n\n .nav-item:focus-visible {\n outline: 2px solid var(--bottom-nav-text-active);\n outline-offset: -2px;\n border-radius: 4px;\n }\n\n .nav-item:active:not([disabled]) {\n transform: scale(0.95);\n }\n\n .nav-item[aria-selected='true'],\n .nav-item.active {\n color: var(--bottom-nav-text-active);\n }\n\n .nav-item[disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n }\n\n .nav-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--bottom-nav-icon-size);\n height: var(--bottom-nav-icon-size);\n flex-shrink: 0;\n }\n\n .nav-icon ::slotted(*),\n .nav-icon svg,\n .nav-icon img {\n width: 100%;\n height: 100%;\n }\n\n .nav-label {\n font-size: var(--bottom-nav-label-size);\n font-weight: 500;\n line-height: 1.2;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n }\n\n /* Hide labels on very small screens */\n @media (max-width: 320px) {\n .nav-label {\n display: none;\n }\n\n .nav-icon {\n width: 28px;\n height: 28px;\n }\n }\n\n /* Color variants */\n :host([color='secondary']) {\n --bottom-nav-text-active: var(--color-secondary, #8b5cf6);\n }\n\n :host([color='success']) {\n --bottom-nav-text-active: var(--color-success, #22c55e);\n }\n\n :host([color='warning']) {\n --bottom-nav-text-active: var(--color-warning, #f59e0b);\n }\n\n :host([color='error']) {\n --bottom-nav-text-active: var(--color-error, #ef4444);\n }\n\n /* Badge indicator for items */\n .nav-item-badge {\n position: absolute;\n top: 4px;\n right: calc(50% - 18px);\n min-width: 8px;\n height: 8px;\n background: var(--color-error, #ef4444);\n border-radius: 50%;\n }\n\n .nav-item-wrapper {\n position: relative;\n flex: 1;\n display: flex;\n justify-content: center;\n max-width: 168px;\n }\n\n /* Slot for custom items */\n ::slotted(el-dm-bottom-navigation-item) {\n flex: 1;\n }\n`;\n\nexport class ElDmBottomNavigation extends BaseElement {\n static properties = {\n items: { type: Array, reflect: false, default: [] },\n value: { type: String, reflect: true },\n color: { type: String, reflect: true, default: 'primary' },\n position: { type: String, reflect: true, default: 'fixed' },\n };\n\n /** Array of navigation items */\n declare items: BottomNavigationItem[];\n\n /** Currently selected item value */\n declare value: string;\n\n /** Color theme */\n declare color: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | string;\n\n /** Position of the navigation bar */\n declare position: 'fixed' | 'static' | 'sticky';\n\n constructor() {\n super();\n this.attachStyles(styles);\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener('click', this._handleClick.bind(this));\n this.addEventListener('keydown', this._handleKeydown.bind(this));\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener('click', this._handleClick.bind(this));\n this.removeEventListener('keydown', this._handleKeydown.bind(this));\n }\n\n /**\n * Handle click events on nav items\n */\n private _handleClick(event: MouseEvent): void {\n const target = event.target as HTMLElement;\n const navItem = target.closest('[data-value]') as HTMLElement | null;\n\n if (!navItem || navItem.hasAttribute('disabled')) {\n return;\n }\n\n const value = navItem.dataset.value;\n if (value && value !== this.value) {\n const item = this.items.find((i) => i.value === value);\n this.value = value;\n this.emit('change', { value, item });\n }\n }\n\n /**\n * Handle keyboard navigation\n */\n private _handleKeydown(event: KeyboardEvent): void {\n const target = event.target as HTMLElement;\n if (!target.classList.contains('nav-item')) return;\n\n const items = Array.from(this.shadowRoot.querySelectorAll('.nav-item:not([disabled])'));\n const currentIndex = items.indexOf(target);\n\n let nextIndex = -1;\n\n switch (event.key) {\n case 'ArrowLeft':\n case 'ArrowUp':\n event.preventDefault();\n nextIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;\n break;\n case 'ArrowRight':\n case 'ArrowDown':\n event.preventDefault();\n nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;\n break;\n case 'Home':\n event.preventDefault();\n nextIndex = 0;\n break;\n case 'End':\n event.preventDefault();\n nextIndex = items.length - 1;\n break;\n case 'Enter':\n case ' ':\n event.preventDefault();\n target.click();\n return;\n }\n\n if (nextIndex >= 0 && items[nextIndex]) {\n (items[nextIndex] as HTMLElement).focus();\n }\n }\n\n /**\n * Render a single navigation item\n */\n private _renderItem(item: BottomNavigationItem, index: number): string {\n const isSelected = item.value === this.value;\n const Tag = item.href ? 'a' : 'button';\n const hrefAttr = item.href ? `href=\"${item.href}\"` : '';\n const disabledAttr = item.disabled ? 'disabled' : '';\n const typeAttr = Tag === 'button' ? 'type=\"button\"' : '';\n\n return `\n <div class=\"nav-item-wrapper\">\n <${Tag}\n class=\"nav-item\"\n part=\"item\"\n role=\"tab\"\n tabindex=\"${item.disabled ? '-1' : '0'}\"\n aria-selected=\"${isSelected}\"\n data-value=\"${item.value}\"\n ${hrefAttr}\n ${disabledAttr}\n ${typeAttr}\n >\n ${\n item.icon\n ? `\n <span class=\"nav-icon\" part=\"icon\">\n ${item.icon}\n </span>\n `\n : ''\n }\n <span class=\"nav-label\" part=\"label\">${item.label}</span>\n </${Tag}>\n </div>\n `;\n }\n\n render(): string {\n const hasItems = this.items && this.items.length > 0;\n\n return `\n <nav class=\"bottom-nav\" part=\"container\" role=\"tablist\" aria-label=\"Bottom navigation\">\n ${hasItems ? this.items.map((item, i) => this._renderItem(item, i)).join('') : '<slot></slot>'}\n </nav>\n `;\n }\n}\n",
6
+ "/**\n * @duskmoon-dev/el-bottom-navigation\n *\n * DuskMoon Bottom Navigation custom element\n */\n\nimport { ElDmBottomNavigation } from './el-dm-bottom-navigation.js';\n\nexport { ElDmBottomNavigation };\nexport type { BottomNavigationItem } from './el-dm-bottom-navigation.js';\n\n/**\n * Register the el-dm-bottom-navigation custom element\n *\n * @example\n * ```ts\n * import { register } from '@duskmoon-dev/el-bottom-navigation';\n * register();\n * ```\n */\nexport function register(): void {\n if (!customElements.get('el-dm-bottom-navigation')) {\n customElements.define('el-dm-bottom-navigation', ElDmBottomNavigation);\n }\n}\n"
7
+ ],
8
+ "mappings": "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",
9
+ "debugId": "F376226EE32C567964756E2164756E21",
10
+ "names": []
11
+ }
@@ -0,0 +1,332 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
6
+ var __toCommonJS = (from) => {
7
+ var entry = __moduleCache.get(from), desc;
8
+ if (entry)
9
+ return entry;
10
+ entry = __defProp({}, "__esModule", { value: true });
11
+ if (from && typeof from === "object" || typeof from === "function")
12
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
13
+ get: () => from[key],
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ }));
16
+ __moduleCache.set(from, entry);
17
+ return entry;
18
+ };
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, {
22
+ get: all[name],
23
+ enumerable: true,
24
+ configurable: true,
25
+ set: (newValue) => all[name] = () => newValue
26
+ });
27
+ };
28
+
29
+ // src/index.ts
30
+ var exports_src = {};
31
+ __export(exports_src, {
32
+ register: () => register,
33
+ ElDmBottomNavigation: () => ElDmBottomNavigation
34
+ });
35
+ module.exports = __toCommonJS(exports_src);
36
+
37
+ // src/el-dm-bottom-navigation.ts
38
+ var import_el_core = require("@duskmoon-dev/el-core");
39
+ var styles = import_el_core.css`
40
+ :host {
41
+ --bottom-nav-height: 56px;
42
+ --bottom-nav-bg: var(--color-surface, #ffffff);
43
+ --bottom-nav-border: var(--color-border, #e5e7eb);
44
+ --bottom-nav-text: var(--color-text-secondary, #6b7280);
45
+ --bottom-nav-text-active: var(--color-primary, #3b82f6);
46
+ --bottom-nav-icon-size: 24px;
47
+ --bottom-nav-label-size: 0.75rem;
48
+ --bottom-nav-shadow: 0 -1px 3px rgba(0, 0, 0, 0.1);
49
+
50
+ display: block;
51
+ position: fixed;
52
+ bottom: 0;
53
+ left: 0;
54
+ right: 0;
55
+ z-index: 1000;
56
+ }
57
+
58
+ :host([hidden]) {
59
+ display: none !important;
60
+ }
61
+
62
+ :host([position='static']) {
63
+ position: static;
64
+ }
65
+
66
+ :host([position='sticky']) {
67
+ position: sticky;
68
+ }
69
+
70
+ .bottom-nav {
71
+ display: flex;
72
+ align-items: center;
73
+ justify-content: space-around;
74
+ height: var(--bottom-nav-height);
75
+ background: var(--bottom-nav-bg);
76
+ border-top: 1px solid var(--bottom-nav-border);
77
+ box-shadow: var(--bottom-nav-shadow);
78
+ padding: 0;
79
+ margin: 0;
80
+ /* Safe area for iOS devices */
81
+ padding-bottom: env(safe-area-inset-bottom, 0);
82
+ }
83
+
84
+ .nav-item {
85
+ flex: 1;
86
+ display: flex;
87
+ flex-direction: column;
88
+ align-items: center;
89
+ justify-content: center;
90
+ gap: 2px;
91
+ padding: 6px 12px;
92
+ min-width: 0;
93
+ max-width: 168px;
94
+ height: 100%;
95
+ background: transparent;
96
+ border: none;
97
+ cursor: pointer;
98
+ color: var(--bottom-nav-text);
99
+ text-decoration: none;
100
+ transition:
101
+ color 0.2s ease,
102
+ transform 0.1s ease;
103
+ -webkit-tap-highlight-color: transparent;
104
+ user-select: none;
105
+ }
106
+
107
+ .nav-item:focus {
108
+ outline: none;
109
+ }
110
+
111
+ .nav-item:focus-visible {
112
+ outline: 2px solid var(--bottom-nav-text-active);
113
+ outline-offset: -2px;
114
+ border-radius: 4px;
115
+ }
116
+
117
+ .nav-item:active:not([disabled]) {
118
+ transform: scale(0.95);
119
+ }
120
+
121
+ .nav-item[aria-selected='true'],
122
+ .nav-item.active {
123
+ color: var(--bottom-nav-text-active);
124
+ }
125
+
126
+ .nav-item[disabled] {
127
+ opacity: 0.5;
128
+ cursor: not-allowed;
129
+ pointer-events: none;
130
+ }
131
+
132
+ .nav-icon {
133
+ display: flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ width: var(--bottom-nav-icon-size);
137
+ height: var(--bottom-nav-icon-size);
138
+ flex-shrink: 0;
139
+ }
140
+
141
+ .nav-icon ::slotted(*),
142
+ .nav-icon svg,
143
+ .nav-icon img {
144
+ width: 100%;
145
+ height: 100%;
146
+ }
147
+
148
+ .nav-label {
149
+ font-size: var(--bottom-nav-label-size);
150
+ font-weight: 500;
151
+ line-height: 1.2;
152
+ white-space: nowrap;
153
+ overflow: hidden;
154
+ text-overflow: ellipsis;
155
+ max-width: 100%;
156
+ }
157
+
158
+ /* Hide labels on very small screens */
159
+ @media (max-width: 320px) {
160
+ .nav-label {
161
+ display: none;
162
+ }
163
+
164
+ .nav-icon {
165
+ width: 28px;
166
+ height: 28px;
167
+ }
168
+ }
169
+
170
+ /* Color variants */
171
+ :host([color='secondary']) {
172
+ --bottom-nav-text-active: var(--color-secondary, #8b5cf6);
173
+ }
174
+
175
+ :host([color='success']) {
176
+ --bottom-nav-text-active: var(--color-success, #22c55e);
177
+ }
178
+
179
+ :host([color='warning']) {
180
+ --bottom-nav-text-active: var(--color-warning, #f59e0b);
181
+ }
182
+
183
+ :host([color='error']) {
184
+ --bottom-nav-text-active: var(--color-error, #ef4444);
185
+ }
186
+
187
+ /* Badge indicator for items */
188
+ .nav-item-badge {
189
+ position: absolute;
190
+ top: 4px;
191
+ right: calc(50% - 18px);
192
+ min-width: 8px;
193
+ height: 8px;
194
+ background: var(--color-error, #ef4444);
195
+ border-radius: 50%;
196
+ }
197
+
198
+ .nav-item-wrapper {
199
+ position: relative;
200
+ flex: 1;
201
+ display: flex;
202
+ justify-content: center;
203
+ max-width: 168px;
204
+ }
205
+
206
+ /* Slot for custom items */
207
+ ::slotted(el-dm-bottom-navigation-item) {
208
+ flex: 1;
209
+ }
210
+ `;
211
+
212
+ class ElDmBottomNavigation extends import_el_core.BaseElement {
213
+ static properties = {
214
+ items: { type: Array, reflect: false, default: [] },
215
+ value: { type: String, reflect: true },
216
+ color: { type: String, reflect: true, default: "primary" },
217
+ position: { type: String, reflect: true, default: "fixed" }
218
+ };
219
+ constructor() {
220
+ super();
221
+ this.attachStyles(styles);
222
+ }
223
+ connectedCallback() {
224
+ super.connectedCallback();
225
+ this.addEventListener("click", this._handleClick.bind(this));
226
+ this.addEventListener("keydown", this._handleKeydown.bind(this));
227
+ }
228
+ disconnectedCallback() {
229
+ super.disconnectedCallback();
230
+ this.removeEventListener("click", this._handleClick.bind(this));
231
+ this.removeEventListener("keydown", this._handleKeydown.bind(this));
232
+ }
233
+ _handleClick(event) {
234
+ const target = event.target;
235
+ const navItem = target.closest("[data-value]");
236
+ if (!navItem || navItem.hasAttribute("disabled")) {
237
+ return;
238
+ }
239
+ const value = navItem.dataset.value;
240
+ if (value && value !== this.value) {
241
+ const item = this.items.find((i) => i.value === value);
242
+ this.value = value;
243
+ this.emit("change", { value, item });
244
+ }
245
+ }
246
+ _handleKeydown(event) {
247
+ const target = event.target;
248
+ if (!target.classList.contains("nav-item"))
249
+ return;
250
+ const items = Array.from(this.shadowRoot.querySelectorAll(".nav-item:not([disabled])"));
251
+ const currentIndex = items.indexOf(target);
252
+ let nextIndex = -1;
253
+ switch (event.key) {
254
+ case "ArrowLeft":
255
+ case "ArrowUp":
256
+ event.preventDefault();
257
+ nextIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
258
+ break;
259
+ case "ArrowRight":
260
+ case "ArrowDown":
261
+ event.preventDefault();
262
+ nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
263
+ break;
264
+ case "Home":
265
+ event.preventDefault();
266
+ nextIndex = 0;
267
+ break;
268
+ case "End":
269
+ event.preventDefault();
270
+ nextIndex = items.length - 1;
271
+ break;
272
+ case "Enter":
273
+ case " ":
274
+ event.preventDefault();
275
+ target.click();
276
+ return;
277
+ }
278
+ if (nextIndex >= 0 && items[nextIndex]) {
279
+ items[nextIndex].focus();
280
+ }
281
+ }
282
+ _renderItem(item, index) {
283
+ const isSelected = item.value === this.value;
284
+ const Tag = item.href ? "a" : "button";
285
+ const hrefAttr = item.href ? `href="${item.href}"` : "";
286
+ const disabledAttr = item.disabled ? "disabled" : "";
287
+ const typeAttr = Tag === "button" ? 'type="button"' : "";
288
+ return `
289
+ <div class="nav-item-wrapper">
290
+ <${Tag}
291
+ class="nav-item"
292
+ part="item"
293
+ role="tab"
294
+ tabindex="${item.disabled ? "-1" : "0"}"
295
+ aria-selected="${isSelected}"
296
+ data-value="${item.value}"
297
+ ${hrefAttr}
298
+ ${disabledAttr}
299
+ ${typeAttr}
300
+ >
301
+ ${item.icon ? `
302
+ <span class="nav-icon" part="icon">
303
+ ${item.icon}
304
+ </span>
305
+ ` : ""}
306
+ <span class="nav-label" part="label">${item.label}</span>
307
+ </${Tag}>
308
+ </div>
309
+ `;
310
+ }
311
+ render() {
312
+ const hasItems = this.items && this.items.length > 0;
313
+ return `
314
+ <nav class="bottom-nav" part="container" role="tablist" aria-label="Bottom navigation">
315
+ ${hasItems ? this.items.map((item, i) => this._renderItem(item, i)).join("") : "<slot></slot>"}
316
+ </nav>
317
+ `;
318
+ }
319
+ }
320
+
321
+ // src/index.ts
322
+ function register() {
323
+ if (!customElements.get("el-dm-bottom-navigation")) {
324
+ customElements.define("el-dm-bottom-navigation", ElDmBottomNavigation);
325
+ }
326
+ }
327
+
328
+ // src/register.ts
329
+ register();
330
+
331
+ //# debugId=D05C25A3A10DC4D464756E2164756E21
332
+ //# sourceMappingURL=register.js.map