Compare commits

..

6 Commits
v3.1.1 ... main

Author SHA1 Message Date
59a870c3bc v3.3.0
Some checks failed
Default (tags) / security (push) Failing after 16s
Default (tags) / test (push) Failing after 17s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-09 08:26:24 +00:00
13fa654c0f feat(dees-appui-base): Add unified App UI API to dees-appui-base with ViewRegistry, AppRouter and StateManager 2025-12-09 08:26:24 +00:00
3616bbb9a7 v3.2.0
Some checks failed
Default (tags) / security (push) Failing after 16s
Default (tags) / test (push) Failing after 17s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-08 20:23:03 +00:00
27c071f7dc feat(dees-simple-appdash,dees-simple-login,dees-terminal): Revamp UI: dashboard & login styling, standardize icons to Lucide, and add terminal background/config 2025-12-08 20:23:03 +00:00
ac1ef4e497 v3.1.2
Some checks failed
Default (tags) / security (push) Failing after 12s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-08 16:17:52 +00:00
9c61c0542b fix(DeesAppuiMainmenu, DeesAppuiSecondarymenu): Add position: relative to main and secondary app UI menus to fix positioning of overlays and tooltips 2025-12-08 16:17:52 +00:00
18 changed files with 1548 additions and 169 deletions

View File

@@ -1,5 +1,34 @@
# Changelog # Changelog
## 2025-12-09 - 3.3.0 - feat(dees-appui-base)
Add unified App UI API to dees-appui-base with ViewRegistry, AppRouter and StateManager
- Introduce ViewRegistry for declarative view registration and rendering (supports tag names, element classes and template functions).
- Add AppRouter with hash/history/external/none modes, URL synchronization, navigate/back/forward and onRouteChange listener support.
- Add StateManager to persist UI state (localStorage, sessionStorage or in-memory) with save/load/update/clear APIs.
- Extend interfaces (interfaces/appconfig.ts) with IAppConfig, IViewDefinition, IRoutingConfig, IStatePersistenceConfig and IAppUIState.
- Expose new public DeesAppuiBase methods: configure, navigateToView, getCurrentView, getUIState, restoreUIState, saveState, loadState, getViewRegistry, getRouter.
- Maintain backward compatibility with existing property-based API and slot usage.
- Export new modules (view.registry, app.router, state.manager) from dees-appui-base index and update element exports.
## 2025-12-08 - 3.2.0 - feat(dees-simple-appdash,dees-simple-login,dees-terminal)
Revamp UI: dashboard & login styling, standardize icons to Lucide, and add terminal background/config
- Standardize icon usage to Lucide prefixes in dees-simple-appdash; add fallback handling for legacy icon names
- Revamped dees-simple-appdash sidebar: updated spacing, typography, header icon wrapper, scrollbar styling, section labels, hover/selected states, and visual indicators
- Change 'Logout' label to 'Sign out' in dees-simple-appdash and add explicit status classes for controlbar (connected, terminal)
- Improve terminal UX: smoother launch/close animations, updated shadow and sizing logic in dees-simple-appdash
- Add background property to dees-terminal, sync it to a CSS variable and apply it to xterm theme for configurable terminal background
- Redesign dees-simple-login: new header/subheader, card layout, spacing, and updated submit text to 'Sign in'
- Bump devDependency @git.zone/tswatch to ^2.3.5
## 2025-12-08 - 3.1.2 - fix(DeesAppuiMainmenu, DeesAppuiSecondarymenu)
Add position: relative to main and secondary app UI menus to fix positioning of overlays and tooltips
- ts_web/elements/00group-appui/dees-appui-mainmenu/dees-appui-mainmenu.ts: add `position: relative` to host styles
- ts_web/elements/00group-appui/dees-appui-secondarymenu/dees-appui-secondarymenu.ts: add `position: relative` to host styles
- Fixes incorrect positioning for absolutely positioned children (tooltips, overlays, badges) inside the main and secondary menus
## 2025-12-08 - 3.1.1 - fix(dees-appui) ## 2025-12-08 - 3.1.1 - fix(dees-appui)
Extract demos for main and secondary app menus, adjust collapsed styles and toggle placement, bump devDependency Extract demos for main and secondary app menus, adjust collapsed styles and toggle placement, bump devDependency

View File

@@ -1,6 +1,6 @@
{ {
"name": "@design.estate/dees-catalog", "name": "@design.estate/dees-catalog",
"version": "3.1.1", "version": "3.3.0",
"private": false, "private": false,
"description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.", "description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.",
"main": "dist_ts_web/index.js", "main": "dist_ts_web/index.js",
@@ -48,7 +48,7 @@
"@git.zone/tsbuild": "^3.1.2", "@git.zone/tsbuild": "^3.1.2",
"@git.zone/tsbundle": "^2.6.3", "@git.zone/tsbundle": "^2.6.3",
"@git.zone/tstest": "^3.1.3", "@git.zone/tstest": "^3.1.3",
"@git.zone/tswatch": "^2.3.2", "@git.zone/tswatch": "^2.3.5",
"@push.rocks/projectinfo": "^5.0.2", "@push.rocks/projectinfo": "^5.0.2",
"@push.rocks/tapbundle": "^6.0.3", "@push.rocks/tapbundle": "^6.0.3",
"@types/node": "^24.10.1" "@types/node": "^24.10.1"

75
pnpm-lock.yaml generated
View File

@@ -98,10 +98,10 @@ importers:
version: 2.6.3 version: 2.6.3
'@git.zone/tstest': '@git.zone/tstest':
specifier: ^3.1.3 specifier: ^3.1.3
version: 3.1.3(@push.rocks/smartserve@1.3.0)(socks@2.8.7)(typescript@5.9.3) version: 3.1.3(@push.rocks/smartserve@1.4.0)(socks@2.8.7)(typescript@5.9.3)
'@git.zone/tswatch': '@git.zone/tswatch':
specifier: ^2.3.2 specifier: ^2.3.5
version: 2.3.2(@tiptap/pm@2.27.1) version: 2.3.5(@tiptap/pm@2.27.1)
'@push.rocks/projectinfo': '@push.rocks/projectinfo':
specifier: ^5.0.2 specifier: ^5.0.2
version: 5.0.2 version: 5.0.2
@@ -126,8 +126,8 @@ packages:
'@api.global/typedserver@3.0.80': '@api.global/typedserver@3.0.80':
resolution: {integrity: sha512-dcp0oXsjBL+XdFg1wUUP08uJQid5bQ0Yv3V3Y3lnI2QCbat0FU+Tsb0TZRnZ4+P150Vj/ITBqJUgDzFsF34grA==} resolution: {integrity: sha512-dcp0oXsjBL+XdFg1wUUP08uJQid5bQ0Yv3V3Y3lnI2QCbat0FU+Tsb0TZRnZ4+P150Vj/ITBqJUgDzFsF34grA==}
'@api.global/typedserver@7.11.0': '@api.global/typedserver@7.11.1':
resolution: {integrity: sha512-DHB3oGRgiLbAeRRFAlXIbjvNACw9YqsfOtLVCPq0nUcGMGSsUQ4SNrJ5OGk9GhVF8bl/jn8SF8w08rnApmZ0uw==} resolution: {integrity: sha512-1vQUJ2/DszDFHVkCmqqBy/qNiIP/jltFN3KxHtoNLxjbdBZYfw1Zd1Odjs6YSPalAD0p8wQ/alJblJEAewNQVg==}
'@api.global/typedsocket@3.1.1': '@api.global/typedsocket@3.1.1':
resolution: {integrity: sha512-Wkz3NlhmfdZMKqXXI2c2dMtGGmSmhdOegZiziL+9b2mqPYdc7Gd8AZRdEOKvbSoIvc9G22/5BEadIWHrfq66TA==} resolution: {integrity: sha512-Wkz3NlhmfdZMKqXXI2c2dMtGGmSmhdOegZiziL+9b2mqPYdc7Gd8AZRdEOKvbSoIvc9G22/5BEadIWHrfq66TA==}
@@ -316,6 +316,9 @@ packages:
'@borewit/text-codec@0.1.1': '@borewit/text-codec@0.1.1':
resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==} resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==}
'@cfworker/json-schema@4.1.1':
resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==}
'@cloudflare/workers-types@4.20251205.0': '@cloudflare/workers-types@4.20251205.0':
resolution: {integrity: sha512-7pup7fYkuQW5XD8RUS/vkxF9SXlrGyCXuZ4ro3uVQvca/GTeSa+8bZ8T4wbq1Aea5lmLIGSlKbhl2msME7bRBA==} resolution: {integrity: sha512-7pup7fYkuQW5XD8RUS/vkxF9SXlrGyCXuZ4ro3uVQvca/GTeSa+8bZ8T4wbq1Aea5lmLIGSlKbhl2msME7bRBA==}
@@ -329,8 +332,8 @@ packages:
'@dabh/diagnostics@2.0.8': '@dabh/diagnostics@2.0.8':
resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==}
'@design.estate/dees-catalog@2.0.7': '@design.estate/dees-catalog@3.1.2':
resolution: {integrity: sha512-rshv71LqA2PXaEEf6C1/hv6Yu2ovRuWaZhdnUznCDpjdYgxBq7PHkiHCNvg/m6wJ9Ue/03HcuuPqtj2bksgAag==} resolution: {integrity: sha512-T4ZhoZzl6NAWGhrz00MDFla5syeMz53euZ+QcBftRMmHuqPHDU/JlBi+K2SmILtnRfWH/lGYWBKugYop6q0chg==}
'@design.estate/dees-comms@1.0.30': '@design.estate/dees-comms@1.0.30':
resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==} resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==}
@@ -552,8 +555,8 @@ packages:
resolution: {integrity: sha512-t+/cKV21JHK8X7NGAmihs5M/eMm+V+jn4R5rzfwGG97WJFAcP5qE1Os9VYtyZw3tx/NZXA2yA4abo/ELluTuRA==} resolution: {integrity: sha512-t+/cKV21JHK8X7NGAmihs5M/eMm+V+jn4R5rzfwGG97WJFAcP5qE1Os9VYtyZw3tx/NZXA2yA4abo/ELluTuRA==}
hasBin: true hasBin: true
'@git.zone/tswatch@2.3.2': '@git.zone/tswatch@2.3.5':
resolution: {integrity: sha512-M4B4D7BlfGb3ssgMHFMHc+hhEmxY6WzNd87ihuLReXMD+twjMA99u2hyAvAnboa3lERlixvHz/nN+rdbGH5VlA==} resolution: {integrity: sha512-wYekG7Q/wg5uptXHPHhVi7dHq19QnLoevQpcpAzF6hMiNbOozN3+4zxOktyJBl6EHUYcFvHXA4fZ4bkJpo5TcA==}
hasBin: true hasBin: true
'@hapi/bourne@3.0.0': '@hapi/bourne@3.0.0':
@@ -981,8 +984,8 @@ packages:
'@push.rocks/smarts3@3.0.3': '@push.rocks/smarts3@3.0.3':
resolution: {integrity: sha512-Y9nXMwurthJ9Z7yi0RwjhPFUC58aY8Mhia8kFo6Xj1tBM4LE8Oxg/ydejF7otHqQGr3QyqV5C4YrDEG17rUuzg==} resolution: {integrity: sha512-Y9nXMwurthJ9Z7yi0RwjhPFUC58aY8Mhia8kFo6Xj1tBM4LE8Oxg/ydejF7otHqQGr3QyqV5C4YrDEG17rUuzg==}
'@push.rocks/smartserve@1.3.0': '@push.rocks/smartserve@1.4.0':
resolution: {integrity: sha512-4ZR9uKVWXVAPzU5wtCQ1mA9jNmOlUl3oGr50EceLT6803UwbNcst7Ek/BhzSaZ0qb2pz0jO5T/V+icgvZ1/5ww==} resolution: {integrity: sha512-cEoXZQSBX3pOv9AyhxRPkrMAWzs2XQhTBmW95BFtTSNzZdji0XgqUu92p7iuF+NVuTFX1QZ8+dbCClLCoRRW7g==}
'@push.rocks/smartshell@3.3.0': '@push.rocks/smartshell@3.3.0':
resolution: {integrity: sha512-m0w618H6YBs+vXGz1CgS4nPi5CUAnqRtckcS9/koGwfcIx1IpjqmiP47BoCTbdgcv0IPUxQVBG1IXTHPuZ8Z5g==} resolution: {integrity: sha512-m0w618H6YBs+vXGz1CgS4nPi5CUAnqRtckcS9/koGwfcIx1IpjqmiP47BoCTbdgcv0IPUxQVBG1IXTHPuZ8Z5g==}
@@ -1020,8 +1023,8 @@ packages:
'@push.rocks/smartversion@3.0.5': '@push.rocks/smartversion@3.0.5':
resolution: {integrity: sha512-8MZSo1yqyaKxKq0Q5N188l4un++9GFWVbhCAX5mXJwewZHn97ujffTeL+eOQYpWFTEpUhaq1QhL4NhqObBCt1Q==} resolution: {integrity: sha512-8MZSo1yqyaKxKq0Q5N188l4un++9GFWVbhCAX5mXJwewZHn97ujffTeL+eOQYpWFTEpUhaq1QhL4NhqObBCt1Q==}
'@push.rocks/smartwatch@5.1.0': '@push.rocks/smartwatch@6.1.1':
resolution: {integrity: sha512-vf4yT8F8V+vstnrr0sh64rGF/8Z8dZlLR4T95KcVtm7F0H3dKxLcvZ3HT0xayw0gQow8+CCtDdts9Wgq5ipJTw==} resolution: {integrity: sha512-wmhLKu9bdpvRcjOfitJOi4jsNKD7S2hVlVq6fAv3IhB2ZbRlSB+Hai4DwOlrdUZaWrg+dFIZU+/ifTOozOPiMg==}
engines: {node: '>=20.0.0'} engines: {node: '>=20.0.0'}
'@push.rocks/smartxml@2.0.0': '@push.rocks/smartxml@2.0.0':
@@ -4656,11 +4659,11 @@ snapshots:
'@push.rocks/webrequest': 3.0.37 '@push.rocks/webrequest': 3.0.37
'@push.rocks/webstream': 1.0.10 '@push.rocks/webstream': 1.0.10
'@api.global/typedserver@3.0.80(@push.rocks/smartserve@1.3.0)': '@api.global/typedserver@3.0.80(@push.rocks/smartserve@1.4.0)':
dependencies: dependencies:
'@api.global/typedrequest': 3.2.5 '@api.global/typedrequest': 3.2.5
'@api.global/typedrequest-interfaces': 3.0.19 '@api.global/typedrequest-interfaces': 3.0.19
'@api.global/typedsocket': 3.1.1(@push.rocks/smartserve@1.3.0) '@api.global/typedsocket': 3.1.1(@push.rocks/smartserve@1.4.0)
'@cloudflare/workers-types': 4.20251205.0 '@cloudflare/workers-types': 4.20251205.0
'@design.estate/dees-comms': 1.0.30 '@design.estate/dees-comms': 1.0.30
'@push.rocks/lik': 6.2.2 '@push.rocks/lik': 6.2.2
@@ -4704,13 +4707,13 @@ snapshots:
- utf-8-validate - utf-8-validate
- vue - vue
'@api.global/typedserver@7.11.0(@tiptap/pm@2.27.1)': '@api.global/typedserver@7.11.1(@tiptap/pm@2.27.1)':
dependencies: dependencies:
'@api.global/typedrequest': 3.2.5 '@api.global/typedrequest': 3.2.5
'@api.global/typedrequest-interfaces': 3.0.19 '@api.global/typedrequest-interfaces': 3.0.19
'@api.global/typedsocket': 4.1.0(@push.rocks/smartserve@1.3.0) '@api.global/typedsocket': 4.1.0(@push.rocks/smartserve@1.4.0)
'@cloudflare/workers-types': 4.20251205.0 '@cloudflare/workers-types': 4.20251205.0
'@design.estate/dees-catalog': 2.0.7(@tiptap/pm@2.27.1) '@design.estate/dees-catalog': 3.1.2(@tiptap/pm@2.27.1)
'@design.estate/dees-comms': 1.0.30 '@design.estate/dees-comms': 1.0.30
'@push.rocks/lik': 6.2.2 '@push.rocks/lik': 6.2.2
'@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartdelay': 3.0.5
@@ -4731,11 +4734,11 @@ snapshots:
'@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrequest': 5.0.1 '@push.rocks/smartrequest': 5.0.1
'@push.rocks/smartrx': 3.0.10 '@push.rocks/smartrx': 3.0.10
'@push.rocks/smartserve': 1.3.0 '@push.rocks/smartserve': 1.4.0
'@push.rocks/smartsitemap': 2.0.4 '@push.rocks/smartsitemap': 2.0.4
'@push.rocks/smartstream': 3.2.5 '@push.rocks/smartstream': 3.2.5
'@push.rocks/smarttime': 4.1.1 '@push.rocks/smarttime': 4.1.1
'@push.rocks/smartwatch': 5.1.0 '@push.rocks/smartwatch': 6.1.1
'@push.rocks/taskbuffer': 3.5.0 '@push.rocks/taskbuffer': 3.5.0
'@push.rocks/webrequest': 4.0.1 '@push.rocks/webrequest': 4.0.1
'@push.rocks/webstore': 2.0.20 '@push.rocks/webstore': 2.0.20
@@ -4750,7 +4753,7 @@ snapshots:
- utf-8-validate - utf-8-validate
- vue - vue
'@api.global/typedsocket@3.1.1(@push.rocks/smartserve@1.3.0)': '@api.global/typedsocket@3.1.1(@push.rocks/smartserve@1.4.0)':
dependencies: dependencies:
'@api.global/typedrequest': 3.2.5 '@api.global/typedrequest': 3.2.5
'@api.global/typedrequest-interfaces': 3.0.19 '@api.global/typedrequest-interfaces': 3.0.19
@@ -4761,7 +4764,7 @@ snapshots:
'@push.rocks/smartstring': 4.1.0 '@push.rocks/smartstring': 4.1.0
'@push.rocks/smarturl': 3.1.0 '@push.rocks/smarturl': 3.1.0
optionalDependencies: optionalDependencies:
'@push.rocks/smartserve': 1.3.0 '@push.rocks/smartserve': 1.4.0
transitivePeerDependencies: transitivePeerDependencies:
- '@nuxt/kit' - '@nuxt/kit'
- bufferutil - bufferutil
@@ -4770,7 +4773,7 @@ snapshots:
- utf-8-validate - utf-8-validate
- vue - vue
'@api.global/typedsocket@4.1.0(@push.rocks/smartserve@1.3.0)': '@api.global/typedsocket@4.1.0(@push.rocks/smartserve@1.4.0)':
dependencies: dependencies:
'@api.global/typedrequest': 3.2.5 '@api.global/typedrequest': 3.2.5
'@api.global/typedrequest-interfaces': 3.0.19 '@api.global/typedrequest-interfaces': 3.0.19
@@ -4779,7 +4782,7 @@ snapshots:
'@push.rocks/smartjson': 5.2.0 '@push.rocks/smartjson': 5.2.0
'@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrx': 3.0.10 '@push.rocks/smartrx': 3.0.10
'@push.rocks/smartserve': 1.3.0 '@push.rocks/smartserve': 1.4.0
'@push.rocks/smartstring': 4.1.0 '@push.rocks/smartstring': 4.1.0
'@push.rocks/smarturl': 3.1.0 '@push.rocks/smarturl': 3.1.0
@@ -5274,6 +5277,8 @@ snapshots:
'@borewit/text-codec@0.1.1': {} '@borewit/text-codec@0.1.1': {}
'@cfworker/json-schema@4.1.1': {}
'@cloudflare/workers-types@4.20251205.0': {} '@cloudflare/workers-types@4.20251205.0': {}
'@colors/colors@1.6.0': {} '@colors/colors@1.6.0': {}
@@ -5288,7 +5293,7 @@ snapshots:
enabled: 2.0.0 enabled: 2.0.0
kuler: 2.0.0 kuler: 2.0.0
'@design.estate/dees-catalog@2.0.7(@tiptap/pm@2.27.1)': '@design.estate/dees-catalog@3.1.2(@tiptap/pm@2.27.1)':
dependencies: dependencies:
'@design.estate/dees-domtools': 2.3.6 '@design.estate/dees-domtools': 2.3.6
'@design.estate/dees-element': 2.1.3 '@design.estate/dees-element': 2.1.3
@@ -5564,9 +5569,9 @@ snapshots:
'@push.rocks/smartshell': 3.3.0 '@push.rocks/smartshell': 3.3.0
tsx: 4.21.0 tsx: 4.21.0
'@git.zone/tstest@3.1.3(@push.rocks/smartserve@1.3.0)(socks@2.8.7)(typescript@5.9.3)': '@git.zone/tstest@3.1.3(@push.rocks/smartserve@1.4.0)(socks@2.8.7)(typescript@5.9.3)':
dependencies: dependencies:
'@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.3.0) '@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.4.0)
'@git.zone/tsbundle': 2.6.3 '@git.zone/tsbundle': 2.6.3
'@git.zone/tsrun': 2.0.0 '@git.zone/tsrun': 2.0.0
'@push.rocks/consolecolor': 2.0.3 '@push.rocks/consolecolor': 2.0.3
@@ -5613,9 +5618,9 @@ snapshots:
- utf-8-validate - utf-8-validate
- vue - vue
'@git.zone/tswatch@2.3.2(@tiptap/pm@2.27.1)': '@git.zone/tswatch@2.3.5(@tiptap/pm@2.27.1)':
dependencies: dependencies:
'@api.global/typedserver': 7.11.0(@tiptap/pm@2.27.1) '@api.global/typedserver': 7.11.1(@tiptap/pm@2.27.1)
'@git.zone/tsbundle': 2.6.3 '@git.zone/tsbundle': 2.6.3
'@git.zone/tsrun': 2.0.0 '@git.zone/tsrun': 2.0.0
'@push.rocks/early': 4.0.4 '@push.rocks/early': 4.0.4
@@ -5626,7 +5631,7 @@ snapshots:
'@push.rocks/smartlog': 3.1.10 '@push.rocks/smartlog': 3.1.10
'@push.rocks/smartlog-destination-local': 9.0.2 '@push.rocks/smartlog-destination-local': 9.0.2
'@push.rocks/smartshell': 3.3.0 '@push.rocks/smartshell': 3.3.0
'@push.rocks/smartwatch': 5.1.0 '@push.rocks/smartwatch': 6.1.1
'@push.rocks/taskbuffer': 3.5.0 '@push.rocks/taskbuffer': 3.5.0
transitivePeerDependencies: transitivePeerDependencies:
- '@nuxt/kit' - '@nuxt/kit'
@@ -6555,9 +6560,10 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- aws-crt - aws-crt
'@push.rocks/smartserve@1.3.0': '@push.rocks/smartserve@1.4.0':
dependencies: dependencies:
'@api.global/typedrequest': 3.2.5 '@api.global/typedrequest': 3.2.5
'@cfworker/json-schema': 4.1.1
'@push.rocks/lik': 6.2.2 '@push.rocks/lik': 6.2.2
'@push.rocks/smartenv': 6.0.0 '@push.rocks/smartenv': 6.0.0
'@push.rocks/smartlog': 3.1.10 '@push.rocks/smartlog': 3.1.10
@@ -6588,7 +6594,7 @@ snapshots:
'@push.rocks/smartsocket@2.1.0': '@push.rocks/smartsocket@2.1.0':
dependencies: dependencies:
'@api.global/typedrequest-interfaces': 3.0.19 '@api.global/typedrequest-interfaces': 3.0.19
'@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.3.0) '@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.4.0)
'@push.rocks/isohash': 2.0.1 '@push.rocks/isohash': 2.0.1
'@push.rocks/isounique': 1.0.5 '@push.rocks/isounique': 1.0.5
'@push.rocks/lik': 6.2.2 '@push.rocks/lik': 6.2.2
@@ -6672,7 +6678,7 @@ snapshots:
'@types/semver': 7.7.1 '@types/semver': 7.7.1
semver: 7.7.3 semver: 7.7.3
'@push.rocks/smartwatch@5.1.0': '@push.rocks/smartwatch@6.1.1':
dependencies: dependencies:
'@push.rocks/lik': 6.2.2 '@push.rocks/lik': 6.2.2
'@push.rocks/smartenv': 6.0.0 '@push.rocks/smartenv': 6.0.0
@@ -6715,20 +6721,17 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- '@aws-sdk/credential-providers' - '@aws-sdk/credential-providers'
- '@mongodb-js/zstd' - '@mongodb-js/zstd'
- '@nuxt/kit'
- aws-crt - aws-crt
- bare-abort-controller - bare-abort-controller
- bufferutil - bufferutil
- gcp-metadata - gcp-metadata
- kerberos - kerberos
- mongodb-client-encryption - mongodb-client-encryption
- react
- react-native-b4a - react-native-b4a
- snappy - snappy
- socks - socks
- supports-color - supports-color
- utf-8-validate - utf-8-validate
- vue
'@push.rocks/taskbuffer@3.5.0': '@push.rocks/taskbuffer@3.5.0':
dependencies: dependencies:

View File

@@ -681,3 +681,123 @@ According to Lit's documentation (https://lit.dev/docs/components/decorators/#de
- Manual testing of key components verified - Manual testing of key components verified
- No regressions detected - No regressions detected
- Focus management and interactions working correctly - Focus management and interactions working correctly
## Enhanced AppUI API (2025-12-08)
The `dees-appui-base` component has been enhanced with a unified configuration API for building real-world applications.
### New Modules:
1. **ViewRegistry** (`view.registry.ts`)
- Manages view definitions and their lifecycle
- Supports tag names, element classes, and template functions as view content
- Methods: register, get, renderView, findByRoute
2. **AppRouter** (`app.router.ts`)
- Built-in routing with hash or history mode
- External router support for framework integration
- Methods: navigate, back, forward, onRouteChange
3. **StateManager** (`state.manager.ts`)
- Persists UI state (collapsed menus, selections, current view)
- Supports localStorage, sessionStorage, or memory storage
- Methods: save, load, update, clear
### New Interfaces (in `interfaces/appconfig.ts`):
```typescript
interface IAppConfig {
branding?: { logoIcon?: string; logoText?: string };
appBar?: IAppBarConfig;
views: IViewDefinition[];
mainMenu?: IMainMenuConfig;
routing?: IRoutingConfig;
statePersistence?: IStatePersistenceConfig;
onViewChange?: (viewId: string, view: IViewDefinition) => void;
}
interface IViewDefinition {
id: string;
name: string;
iconName?: string;
content: string | (new () => HTMLElement) | (() => TemplateResult);
secondaryMenu?: ISecondaryMenuGroup[];
contentTabs?: ITab[];
route?: string;
}
interface IRoutingConfig {
mode: 'hash' | 'history' | 'external' | 'none';
basePath?: string;
defaultView?: string;
syncUrl?: boolean;
}
```
### New Public Methods on DeesAppuiBase:
```typescript
// Configure with unified config
configure(config: IAppConfig): void
// Navigation
navigateToView(viewId: string): boolean
getCurrentView(): IViewDefinition | undefined
// State management
getUIState(): IAppUIState
restoreUIState(state: IAppUIState): void
saveState(): void
loadState(): boolean
// Access internals
getViewRegistry(): ViewRegistry
getRouter(): AppRouter | null
```
### Usage Example (New Unified Config API):
```typescript
import type { IAppConfig } from '@design.estate/dees-catalog';
const config: IAppConfig = {
branding: { logoIcon: 'lucide:box', logoText: 'My App' },
views: [
{ id: 'dashboard', name: 'Dashboard', iconName: 'lucide:home', content: 'my-dashboard' },
{ id: 'settings', name: 'Settings', iconName: 'lucide:settings', content: 'my-settings' },
],
mainMenu: {
sections: [{ views: ['dashboard'] }],
bottomItems: ['settings'],
},
routing: { mode: 'hash', defaultView: 'dashboard' },
statePersistence: { enabled: true, storage: 'localStorage' },
};
html`<dees-appui-base .config=${config}></dees-appui-base>`;
```
### Backward Compatibility:
The existing property-based API still works:
```typescript
html`
<dees-appui-base
.mainmenuGroups=${groups}
.secondarymenuGroups=${secondaryGroups}
@mainmenu-tab-select=${handler}
>
<div slot="maincontent">...</div>
</dees-appui-base>
`;
```
### Key Features:
- **Declarative View Registry**: Map menu items to view components
- **Built-in Routing**: Hash or history mode with URL synchronization
- **External Router Support**: Integrate with Angular Router or other frameworks
- **State Persistence**: Save/restore collapsed menus, selections, and current view
- **View-specific Menus**: Each view can define its own secondary menu and tabs
- **Full Backward Compatibility**: Existing code continues to work

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@design.estate/dees-catalog', name: '@design.estate/dees-catalog',
version: '3.1.1', version: '3.3.0',
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.' description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
} }

View File

@@ -0,0 +1,271 @@
import type { IRoutingConfig, IViewDefinition } from '../../interfaces/appconfig.js';
import type { ViewRegistry } from './view.registry.js';
export type TRouteChangeCallback = (viewId: string, params?: Record<string, string>) => void;
/**
* Router for managing view navigation and URL synchronization
*/
export class AppRouter {
private config: Required<Omit<IRoutingConfig, 'notFound'>> & Pick<IRoutingConfig, 'notFound'>;
private viewRegistry: ViewRegistry;
private listeners: Set<TRouteChangeCallback> = new Set();
private currentViewId: string | null = null;
private isInitialized: boolean = false;
constructor(config: IRoutingConfig, viewRegistry: ViewRegistry) {
this.config = {
mode: config.mode,
basePath: config.basePath || '',
defaultView: config.defaultView || '',
syncUrl: config.syncUrl ?? true,
notFound: config.notFound,
};
this.viewRegistry = viewRegistry;
}
/**
* Initialize the router
*/
public init(): void {
if (this.isInitialized) return;
if (this.config.mode === 'hash') {
window.addEventListener('hashchange', this.handleHashChange);
// Check initial hash
const initialView = this.getViewFromHash();
if (initialView) {
this.navigate(initialView, { source: 'initial' });
} else if (this.config.defaultView) {
this.navigate(this.config.defaultView, { source: 'initial' });
}
} else if (this.config.mode === 'history') {
window.addEventListener('popstate', this.handlePopState);
// Check initial path
const initialView = this.getViewFromPath();
if (initialView) {
this.navigate(initialView, { source: 'initial' });
} else if (this.config.defaultView) {
this.navigate(this.config.defaultView, { source: 'initial' });
}
} else if (this.config.mode === 'none' && this.config.defaultView) {
this.navigate(this.config.defaultView, { source: 'initial' });
}
// For 'external' mode, we don't set up listeners - the external router handles it
this.isInitialized = true;
}
/**
* Navigate to a view by ID
*/
public navigate(
viewId: string,
options: {
source?: 'navigation' | 'popstate' | 'initial' | 'programmatic';
replace?: boolean;
params?: Record<string, string>;
} = {}
): boolean {
const { source = 'programmatic', replace = false, params } = options;
const view = this.viewRegistry.get(viewId);
if (!view) {
console.warn(`Cannot navigate to unknown view: ${viewId}`);
if (this.config.notFound) {
if (typeof this.config.notFound === 'function') {
this.config.notFound();
} else {
return this.navigate(this.config.notFound, { source, replace: true });
}
}
return false;
}
const previousViewId = this.currentViewId;
this.currentViewId = viewId;
// Update URL if configured
if (this.config.syncUrl && this.config.mode !== 'none' && this.config.mode !== 'external') {
this.updateUrl(view, replace);
}
// Notify listeners
this.notifyListeners(viewId, params);
return true;
}
/**
* Navigate back in history
*/
public back(): void {
if (this.config.mode === 'hash' || this.config.mode === 'history') {
window.history.back();
}
}
/**
* Navigate forward in history
*/
public forward(): void {
if (this.config.mode === 'hash' || this.config.mode === 'history') {
window.history.forward();
}
}
/**
* Get current view ID
*/
public getCurrentViewId(): string | null {
return this.currentViewId;
}
/**
* Add a route change listener
*/
public onRouteChange(callback: TRouteChangeCallback): () => void {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
/**
* Handle external navigation (for external router mode)
*/
public handleExternalNavigation(viewId: string, params?: Record<string, string>): void {
if (this.config.mode !== 'external') {
console.warn('handleExternalNavigation should only be used in external mode');
}
const previousViewId = this.currentViewId;
this.currentViewId = viewId;
this.notifyListeners(viewId, params);
}
/**
* Sync state with URL (for external router integration)
*/
public syncWithUrl(): string | null {
if (this.config.mode === 'hash') {
return this.getViewFromHash();
} else if (this.config.mode === 'history') {
return this.getViewFromPath();
}
return null;
}
/**
* Get the current route from the URL
*/
public getCurrentRoute(): string {
if (this.config.mode === 'hash') {
return window.location.hash.slice(1) || '';
} else if (this.config.mode === 'history') {
let path = window.location.pathname;
if (this.config.basePath && path.startsWith(this.config.basePath)) {
path = path.slice(this.config.basePath.length);
}
return path.replace(/^\//, '');
}
return '';
}
/**
* Build a URL for a view
*/
public buildUrl(viewId: string): string {
const view = this.viewRegistry.get(viewId);
const route = view?.route || viewId;
if (this.config.mode === 'hash') {
return `#${route}`;
} else if (this.config.mode === 'history') {
return `${this.config.basePath}/${route}`;
}
return '';
}
/**
* Destroy the router
*/
public destroy(): void {
if (this.config.mode === 'hash') {
window.removeEventListener('hashchange', this.handleHashChange);
} else if (this.config.mode === 'history') {
window.removeEventListener('popstate', this.handlePopState);
}
this.listeners.clear();
this.isInitialized = false;
}
// Private methods
private handleHashChange = (): void => {
const viewId = this.getViewFromHash();
if (viewId && viewId !== this.currentViewId) {
this.navigate(viewId, { source: 'popstate' });
}
};
private handlePopState = (): void => {
const viewId = this.getViewFromPath();
if (viewId && viewId !== this.currentViewId) {
this.navigate(viewId, { source: 'popstate' });
}
};
private getViewFromHash(): string | null {
const hash = window.location.hash.slice(1); // Remove #
if (!hash) return null;
// Try to find view by route
const view = this.viewRegistry.findByRoute(hash);
return view?.id || null;
}
private getViewFromPath(): string | null {
let path = window.location.pathname;
// Remove base path if configured
if (this.config.basePath) {
if (path.startsWith(this.config.basePath)) {
path = path.slice(this.config.basePath.length);
}
}
// Remove leading slash
path = path.replace(/^\//, '');
if (!path) return null;
const view = this.viewRegistry.findByRoute(path);
return view?.id || null;
}
private updateUrl(view: IViewDefinition, replace: boolean): void {
const route = view.route || view.id;
if (this.config.mode === 'hash') {
const newHash = `#${route}`;
if (replace) {
window.history.replaceState(null, '', newHash);
} else {
window.history.pushState(null, '', newHash);
}
} else if (this.config.mode === 'history') {
const basePath = this.config.basePath || '';
const newPath = `${basePath}/${route}`;
if (replace) {
window.history.replaceState({ viewId: view.id }, '', newPath);
} else {
window.history.pushState({ viewId: view.id }, '', newPath);
}
}
}
private notifyListeners(viewId: string, params?: Record<string, string>): void {
for (const listener of this.listeners) {
listener(viewId, params);
}
}
}

View File

@@ -17,6 +17,11 @@ import type { DeesAppuiMaincontent } from '../dees-appui-maincontent/dees-appui-
import type { DeesAppuiActivitylog } from '../dees-appui-activitylog/dees-appui-activitylog.js'; import type { DeesAppuiActivitylog } from '../dees-appui-activitylog/dees-appui-activitylog.js';
import { demoFunc } from './dees-appui-base.demo.js'; import { demoFunc } from './dees-appui-base.demo.js';
// New module imports
import { ViewRegistry } from './view.registry.js';
import { AppRouter } from './app.router.js';
import { StateManager } from './state.manager.js';
// Import child components // Import child components
import '../dees-appui-appbar/index.js'; import '../dees-appui-appbar/index.js';
import '../dees-appui-mainmenu/dees-appui-mainmenu.js'; import '../dees-appui-mainmenu/dees-appui-mainmenu.js';
@@ -116,6 +121,19 @@ export class DeesAppuiBase extends DeesElement {
@state() @state()
accessor activitylog: DeesAppuiActivitylog | undefined = undefined; accessor activitylog: DeesAppuiActivitylog | undefined = undefined;
// NEW: Unified config property
@property({ type: Object })
accessor config: interfaces.IAppConfig | undefined = undefined;
// NEW: Current view state
@state()
accessor currentView: interfaces.IViewDefinition | undefined = undefined;
// NEW: Internal services (not reactive, managed internally)
private viewRegistry: ViewRegistry = new ViewRegistry();
private router: AppRouter | null = null;
private stateManager: StateManager | null = null;
public static styles = [ public static styles = [
cssManager.defaultStyles, cssManager.defaultStyles,
css` css`
@@ -155,6 +173,15 @@ export class DeesAppuiBase extends DeesElement {
position: relative; position: relative;
z-index: 1; z-index: 1;
} }
/* View container for dynamically loaded views */
.view-container {
display: contents;
}
.view-container:empty {
display: none;
}
`, `,
]; ];
@@ -200,6 +227,7 @@ export class DeesAppuiBase extends DeesElement {
<dees-appui-maincontent <dees-appui-maincontent
.tabs=${this.maincontentTabs} .tabs=${this.maincontentTabs}
> >
<div class="view-container"></div>
<slot name="maincontent"></slot> <slot name="maincontent"></slot>
</dees-appui-maincontent> </dees-appui-maincontent>
<dees-appui-activitylog></dees-appui-activitylog> <dees-appui-activitylog></dees-appui-activitylog>
@@ -214,6 +242,24 @@ export class DeesAppuiBase extends DeesElement {
this.secondarymenu = this.shadowRoot.querySelector('dees-appui-secondarymenu'); this.secondarymenu = this.shadowRoot.querySelector('dees-appui-secondarymenu');
this.maincontent = this.shadowRoot.querySelector('dees-appui-maincontent'); this.maincontent = this.shadowRoot.querySelector('dees-appui-maincontent');
this.activitylog = this.shadowRoot.querySelector('dees-appui-activitylog'); this.activitylog = this.shadowRoot.querySelector('dees-appui-activitylog');
// Initialize from config if provided
if (this.config) {
this.applyConfig(this.config);
// Restore state if enabled
if (this.config.statePersistence?.enabled) {
this.loadState();
}
// Initialize router after state restore
this.router?.init();
}
}
async disconnectedCallback() {
await super.disconnectedCallback();
this.router?.destroy();
} }
// Event handlers for appbar // Event handlers for appbar
@@ -295,4 +341,242 @@ export class DeesAppuiBase extends DeesElement {
composed: true composed: true
})); }));
} }
// ==========================================
// NEW: Public methods for unified config API
// ==========================================
/**
* Configure the app shell with a unified config object
*/
public configure(config: interfaces.IAppConfig): void {
this.config = config;
this.applyConfig(config);
}
/**
* Navigate to a view by ID
*/
public navigateToView(viewId: string): boolean {
if (this.router) {
return this.router.navigate(viewId);
}
// Fallback for non-routed mode
const view = this.viewRegistry.get(viewId);
if (view) {
this.loadView(view);
return true;
}
return false;
}
/**
* Get the current view
*/
public getCurrentView(): interfaces.IViewDefinition | undefined {
return this.currentView;
}
/**
* Get UI state for serialization
*/
public getUIState(): interfaces.IAppUIState {
return {
currentViewId: this.currentView?.id,
mainMenuCollapsed: this.mainmenuCollapsed,
secondaryMenuCollapsed: this.secondarymenuCollapsed,
secondaryMenuSelectedKey: this.secondarymenuSelectedItem?.key,
collapsedGroups: [], // TODO: Get from secondarymenu if needed
timestamp: Date.now(),
};
}
/**
* Restore UI state from a state object
*/
public restoreUIState(state: interfaces.IAppUIState): void {
if (state.mainMenuCollapsed !== undefined) {
this.mainmenuCollapsed = state.mainMenuCollapsed;
}
if (state.secondaryMenuCollapsed !== undefined) {
this.secondarymenuCollapsed = state.secondaryMenuCollapsed;
}
if (state.currentViewId) {
this.navigateToView(state.currentViewId);
}
}
/**
* Save current UI state
*/
public saveState(): void {
this.stateManager?.save(this.getUIState());
}
/**
* Load and restore saved UI state
*/
public loadState(): boolean {
const state = this.stateManager?.load();
if (state) {
this.restoreUIState(state);
return true;
}
return false;
}
/**
* Get access to the view registry
*/
public getViewRegistry(): ViewRegistry {
return this.viewRegistry;
}
/**
* Get access to the router
*/
public getRouter(): AppRouter | null {
return this.router;
}
// ==========================================
// NEW: Private helper methods
// ==========================================
private applyConfig(config: interfaces.IAppConfig): void {
// Register views
if (config.views) {
this.viewRegistry.clear();
this.viewRegistry.registerAll(config.views);
}
// Apply branding
if (config.branding) {
this.mainmenuLogoIcon = config.branding.logoIcon || '';
this.mainmenuLogoText = config.branding.logoText || '';
}
// Apply app bar config
if (config.appBar) {
this.appbarMenuItems = config.appBar.menuItems || [];
this.appbarBreadcrumbs = config.appBar.breadcrumbs || '';
this.appbarBreadcrumbSeparator = config.appBar.breadcrumbSeparator || ' > ';
this.appbarShowWindowControls = config.appBar.showWindowControls ?? true;
this.appbarShowSearch = config.appBar.showSearch ?? false;
this.appbarUser = config.appBar.user;
this.appbarProfileMenuItems = config.appBar.profileMenuItems || [];
}
// Build main menu from view references
if (config.mainMenu) {
this.mainmenuGroups = this.buildMainMenuGroups(config);
this.mainmenuBottomTabs = this.buildBottomTabs(config);
}
// Initialize state manager
if (config.statePersistence) {
this.stateManager = new StateManager(config.statePersistence);
}
// Initialize router
if (config.routing && config.routing.mode !== 'none') {
this.router = new AppRouter(config.routing, this.viewRegistry);
this.router.onRouteChange((viewId) => {
const view = this.viewRegistry.get(viewId);
if (view) {
this.loadView(view);
}
});
}
// Bind event callbacks
if (config.onViewChange) {
this.addEventListener('view-change', ((e: CustomEvent) => {
config.onViewChange!(e.detail.viewId, e.detail.view);
}) as EventListener);
}
if (config.onSearch) {
this.addEventListener('appbar-search-click', () => {
config.onSearch!();
});
}
}
private buildMainMenuGroups(config: interfaces.IAppConfig): interfaces.IMenuGroup[] {
if (!config.mainMenu?.sections) return [];
return config.mainMenu.sections.map((section) => ({
name: section.name,
tabs: section.views
.map((viewId) => {
const view = this.viewRegistry.get(viewId);
if (!view) {
console.warn(`View "${viewId}" not found in registry`);
return null;
}
return {
key: view.name,
iconName: view.iconName,
action: () => this.navigateToView(viewId),
};
})
.filter(Boolean) as interfaces.ITab[],
}));
}
private buildBottomTabs(config: interfaces.IAppConfig): interfaces.ITab[] {
if (!config.mainMenu?.bottomItems) return [];
return config.mainMenu.bottomItems
.map((viewId) => {
const view = this.viewRegistry.get(viewId);
if (!view) {
console.warn(`View "${viewId}" not found in registry`);
return null;
}
return {
key: view.name,
iconName: view.iconName,
action: () => this.navigateToView(viewId),
};
})
.filter(Boolean) as interfaces.ITab[];
}
private loadView(view: interfaces.IViewDefinition): void {
const previousView = this.currentView;
this.currentView = view;
// Update secondary menu
if (view.secondaryMenu) {
this.secondarymenuGroups = view.secondaryMenu;
this.secondarymenuHeading = view.name;
}
// Update content tabs
if (view.contentTabs) {
this.maincontentTabs = view.contentTabs;
}
// Render view content into the view container
const viewContainer = this.maincontent?.shadowRoot?.querySelector('.view-container')
|| this.shadowRoot?.querySelector('.view-container');
if (viewContainer) {
this.viewRegistry.renderView(view.id, viewContainer as HTMLElement);
}
// Save state if configured
this.stateManager?.update({ currentViewId: view.id });
// Dispatch event
this.dispatchEvent(
new CustomEvent('view-change', {
detail: { viewId: view.id, view, previousView },
bubbles: true,
composed: true,
})
);
}
} }

View File

@@ -1 +1,4 @@
export * from './dees-appui-base.js'; export * from './dees-appui-base.js';
export * from './view.registry.js';
export * from './app.router.js';
export * from './state.manager.js';

View File

@@ -0,0 +1,185 @@
import type { IStatePersistenceConfig, IAppUIState } from '../../interfaces/appconfig.js';
/**
* Manager for persisting and restoring UI state
*/
export class StateManager {
private config: Required<IStatePersistenceConfig>;
private memoryStorage: Map<string, string> = new Map();
constructor(config: IStatePersistenceConfig = { enabled: false }) {
this.config = {
enabled: config.enabled,
storageKey: config.storageKey || 'dees-appui-state',
storage: config.storage || 'localStorage',
persist: {
mainMenuCollapsed: true,
secondaryMenuCollapsed: true,
selectedView: true,
secondaryMenuSelection: true,
collapsedGroups: true,
...config.persist,
},
};
}
/**
* Check if state persistence is enabled
*/
public isEnabled(): boolean {
return this.config.enabled;
}
/**
* Save current UI state
*/
public save(state: Partial<IAppUIState>): void {
if (!this.config.enabled) return;
const existingState = this.load() || {};
const newState: IAppUIState = {
...existingState,
timestamp: Date.now(),
};
// Only save what's configured
if (this.config.persist.selectedView && state.currentViewId !== undefined) {
newState.currentViewId = state.currentViewId;
}
if (this.config.persist.mainMenuCollapsed && state.mainMenuCollapsed !== undefined) {
newState.mainMenuCollapsed = state.mainMenuCollapsed;
}
if (this.config.persist.secondaryMenuCollapsed && state.secondaryMenuCollapsed !== undefined) {
newState.secondaryMenuCollapsed = state.secondaryMenuCollapsed;
}
if (this.config.persist.secondaryMenuSelection && state.secondaryMenuSelectedKey !== undefined) {
newState.secondaryMenuSelectedKey = state.secondaryMenuSelectedKey;
}
if (this.config.persist.collapsedGroups && state.collapsedGroups !== undefined) {
newState.collapsedGroups = state.collapsedGroups;
}
this.setItem(this.config.storageKey, JSON.stringify(newState));
}
/**
* Load persisted UI state
*/
public load(): IAppUIState | null {
if (!this.config.enabled) return null;
try {
const data = this.getItem(this.config.storageKey);
if (!data) return null;
return JSON.parse(data) as IAppUIState;
} catch (e) {
console.warn('Failed to load UI state:', e);
return null;
}
}
/**
* Clear persisted state
*/
public clear(): void {
this.removeItem(this.config.storageKey);
}
/**
* Check if state exists
*/
public hasState(): boolean {
return this.getItem(this.config.storageKey) !== null;
}
/**
* Get state age in milliseconds
*/
public getStateAge(): number | null {
const state = this.load();
if (!state?.timestamp) return null;
return Date.now() - state.timestamp;
}
/**
* Update specific state properties
*/
public update(updates: Partial<IAppUIState>): void {
const currentState = this.load() || {};
this.save({ ...currentState, ...updates });
}
/**
* Get the storage key being used
*/
public getStorageKey(): string {
return this.config.storageKey;
}
// Storage abstraction methods
private getItem(key: string): string | null {
switch (this.config.storage) {
case 'localStorage':
try {
return localStorage.getItem(key);
} catch {
return null;
}
case 'sessionStorage':
try {
return sessionStorage.getItem(key);
} catch {
return null;
}
case 'memory':
return this.memoryStorage.get(key) || null;
default:
return null;
}
}
private setItem(key: string, value: string): void {
switch (this.config.storage) {
case 'localStorage':
try {
localStorage.setItem(key, value);
} catch (e) {
console.warn('Failed to save to localStorage:', e);
}
break;
case 'sessionStorage':
try {
sessionStorage.setItem(key, value);
} catch (e) {
console.warn('Failed to save to sessionStorage:', e);
}
break;
case 'memory':
this.memoryStorage.set(key, value);
break;
}
}
private removeItem(key: string): void {
switch (this.config.storage) {
case 'localStorage':
try {
localStorage.removeItem(key);
} catch {
// Ignore
}
break;
case 'sessionStorage':
try {
sessionStorage.removeItem(key);
} catch {
// Ignore
}
break;
case 'memory':
this.memoryStorage.delete(key);
break;
}
}
}

View File

@@ -0,0 +1,167 @@
import { html, render, type TemplateResult } from '@design.estate/dees-element';
import type { IViewDefinition } from '../../interfaces/appconfig.js';
/**
* Registry for managing views and their lifecycle
*/
export class ViewRegistry {
private views: Map<string, IViewDefinition> = new Map();
private instances: Map<string, HTMLElement> = new Map();
private currentViewId: string | null = null;
/**
* Register a single view
*/
public register(view: IViewDefinition): void {
if (this.views.has(view.id)) {
console.warn(`View with id "${view.id}" already registered. Overwriting.`);
}
this.views.set(view.id, view);
}
/**
* Register multiple views
*/
public registerAll(views: IViewDefinition[]): void {
views.forEach((view) => this.register(view));
}
/**
* Get a view definition by ID
*/
public get(viewId: string): IViewDefinition | undefined {
return this.views.get(viewId);
}
/**
* Get all registered view IDs
*/
public getViewIds(): string[] {
return Array.from(this.views.keys());
}
/**
* Get all views
*/
public getAll(): IViewDefinition[] {
return Array.from(this.views.values());
}
/**
* Get route for a view
*/
public getRoute(viewId: string): string {
const view = this.views.get(viewId);
return view?.route || view?.id || '';
}
/**
* Find view by route
*/
public findByRoute(route: string): IViewDefinition | undefined {
for (const view of this.views.values()) {
const viewRoute = view.route || view.id;
if (viewRoute === route) {
return view;
}
}
return undefined;
}
/**
* Render a view's content into a container
*/
public renderView(viewId: string, container: HTMLElement): HTMLElement | null {
const view = this.views.get(viewId);
if (!view) {
console.error(`View "${viewId}" not found in registry`);
return null;
}
// Clear container
container.innerHTML = '';
let element: HTMLElement;
if (typeof view.content === 'string') {
// Tag name string
element = document.createElement(view.content);
} else if (typeof view.content === 'function') {
// Check if it's a class constructor or template function
if (view.content.prototype instanceof HTMLElement) {
// Element class constructor
element = new (view.content as new () => HTMLElement)();
} else {
// Template function - wrap in a container and use Lit's render
const wrapper = document.createElement('div');
wrapper.className = 'view-content-wrapper';
wrapper.style.cssText = 'display: contents;';
const template = (view.content as () => TemplateResult)();
render(template, wrapper);
element = wrapper;
}
} else {
console.error(`Invalid content type for view "${viewId}"`);
return null;
}
container.appendChild(element);
this.instances.set(viewId, element);
this.currentViewId = viewId;
return element;
}
/**
* Get currently active view ID
*/
public getCurrentViewId(): string | null {
return this.currentViewId;
}
/**
* Get cached instance of a view
*/
public getInstance(viewId: string): HTMLElement | undefined {
return this.instances.get(viewId);
}
/**
* Clear all instances
*/
public clearInstances(): void {
this.instances.clear();
this.currentViewId = null;
}
/**
* Unregister a view
*/
public unregister(viewId: string): boolean {
this.instances.delete(viewId);
return this.views.delete(viewId);
}
/**
* Clear the registry
*/
public clear(): void {
this.views.clear();
this.instances.clear();
this.currentViewId = null;
}
/**
* Check if a view is registered
*/
public has(viewId: string): boolean {
return this.views.has(viewId);
}
/**
* Get the number of registered views
*/
public get size(): number {
return this.views.size;
}
}

View File

@@ -57,6 +57,7 @@ export class DeesAppuiMainmenu extends DeesElement {
--menu-width-collapsed: 56px; --menu-width-collapsed: 56px;
--tooltip-bg: ${cssManager.bdTheme('#18181b', '#fafafa')}; --tooltip-bg: ${cssManager.bdTheme('#18181b', '#fafafa')};
--tooltip-fg: ${cssManager.bdTheme('#fafafa', '#18181b')}; --tooltip-fg: ${cssManager.bdTheme('#fafafa', '#18181b')};
position: relative;
display: block; display: block;
height: 100%; height: 100%;
} }

View File

@@ -77,6 +77,7 @@ export class DeesAppuiSecondarymenu extends DeesElement {
--badge-error-bg: ${cssManager.bdTheme('#fee2e2', '#450a0a')}; --badge-error-bg: ${cssManager.bdTheme('#fee2e2', '#450a0a')};
--badge-error-fg: ${cssManager.bdTheme('#991b1b', '#f87171')}; --badge-error-fg: ${cssManager.bdTheme('#991b1b', '#f87171')};
position: relative;
display: block; display: block;
height: 100%; height: 100%;
width: var(--sidebar-width-expanded); width: var(--sidebar-width-expanded);

View File

@@ -266,17 +266,17 @@ export const demoFunc = () => html`
.viewTabs=${[ .viewTabs=${[
{ {
name: 'Dashboard', name: 'Dashboard',
iconName: 'home', iconName: 'lucide:home',
element: DemoViewDashboard, element: DemoViewDashboard,
}, },
{ {
name: 'Analytics', name: 'Analytics',
iconName: 'lineChart', iconName: 'lucide:lineChart',
element: DemoViewAnalytics, element: DemoViewAnalytics,
}, },
{ {
name: 'Settings', name: 'Settings',
iconName: 'settings', iconName: 'lucide:settings',
element: DemoViewSettings, element: DemoViewSettings,
} }
] as IView[]} ] as IView[]}

View File

@@ -51,7 +51,7 @@ export class DeesSimpleAppDash extends DeesElement {
cssManager.defaultStyles, cssManager.defaultStyles,
css` css`
:host { :host {
color: ${cssManager.bdTheme('#333', '#ccc')}; color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
user-select: none; user-select: none;
display: block; display: block;
overflow: hidden; overflow: hidden;
@@ -75,67 +75,122 @@ export class DeesSimpleAppDash extends DeesElement {
left: 0px; left: 0px;
height: calc(100% - 24px); height: calc(100% - 24px);
width: 240px; width: 240px;
background: ${cssManager.bdTheme('#fafafa', '#000')}; background: ${cssManager.bdTheme('hsl(0 0% 99%)', 'hsl(0 0% 7%)')};
border-right: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')}; border-right: 1px solid ${cssManager.bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 13%)')};
font-size: 12px; font-size: 13px;
font-family: 'Geist Sans', sans-serif; font-family: 'Geist Sans', sans-serif;
z-index: 2; z-index: 2;
display: grid; display: grid;
grid-template-rows: auto min-content; grid-template-rows: auto 1fr min-content;
overflow: hidden; overflow: hidden;
} }
.sidebar-header { .sidebar-header {
padding: 16px 12px; padding: 20px 16px;
border-bottom: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')}; border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 13%)')};
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 12px;
}
.header-icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 10px;
background: ${cssManager.bdTheme(
'linear-gradient(135deg, hsl(215 20% 95%) 0%, hsl(215 20% 90%) 100%)',
'linear-gradient(135deg, hsl(215 20% 18%) 0%, hsl(215 20% 14%) 100%)'
)};
box-shadow: ${cssManager.bdTheme(
'0 1px 2px rgb(0 0 0 / 0.05), inset 0 1px 0 rgb(255 255 255 / 0.5)',
'0 1px 2px rgb(0 0 0 / 0.2), inset 0 1px 0 rgb(255 255 255 / 0.05)'
)};
}
.header-icon-wrapper dees-icon {
font-size: 18px;
color: ${cssManager.bdTheme('hsl(215 20% 40%)', 'hsl(215 20% 70%)')};
} }
.appName { .appName {
font-size: 14px; font-size: 15px;
font-weight: 600; font-weight: 600;
color: ${cssManager.bdTheme('#000', '#fff')}; color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 98%)')};
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
letter-spacing: -0.02em;
} }
.viewTabs-container { .viewTabs-container {
flex: 1;
overflow-y: auto; overflow-y: auto;
padding: 4px 0; padding: 12px 8px;
scrollbar-width: thin;
scrollbar-color: ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 20%)')} transparent;
}
.viewTabs-container::-webkit-scrollbar {
width: 6px;
}
.viewTabs-container::-webkit-scrollbar-track {
background: transparent;
}
.viewTabs-container::-webkit-scrollbar-thumb {
background: ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 20%)')};
border-radius: 3px;
}
.viewTabs-container::-webkit-scrollbar-thumb:hover {
background: ${cssManager.bdTheme('hsl(0 0% 75%)', 'hsl(0 0% 30%)')};
}
.section-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 50%)')};
padding: 8px 12px 8px;
margin-bottom: 4px;
} }
.viewTabs { .viewTabs {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 2px;
} }
.viewTab { .viewTab {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 10px;
padding: 8px 12px; padding: 10px 12px;
cursor: default; cursor: default;
transition: background 0.1s; transition: all 0.15s ease;
color: ${cssManager.bdTheme('#333', '#ccc')}; color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 65%)')};
user-select: none; user-select: none;
position: relative; position: relative;
border-radius: 8px;
} }
.viewTab:hover { .viewTab:hover {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.08)')}; background: ${cssManager.bdTheme('hsl(0 0% 0% / 0.04)', 'hsl(0 0% 100% / 0.05)')};
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
} }
.viewTab:active { .viewTab:active {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.12)')}; background: ${cssManager.bdTheme('hsl(0 0% 0% / 0.06)', 'hsl(0 0% 100% / 0.07)')};
transform: scale(0.99);
} }
.viewTab.selected { .viewTab.selected {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.12)')}; background: ${cssManager.bdTheme('hsl(215 25% 95%)', 'hsl(215 20% 15%)')};
color: ${cssManager.bdTheme('#000', '#fff')}; color: ${cssManager.bdTheme('hsl(215 25% 30%)', 'hsl(215 25% 85%)')};
font-weight: 500; font-weight: 500;
} }
@@ -143,41 +198,65 @@ export class DeesSimpleAppDash extends DeesElement {
content: ''; content: '';
position: absolute; position: absolute;
left: 0; left: 0;
top: 0; top: 8px;
bottom: 0; bottom: 8px;
width: 3px; width: 3px;
background: ${cssManager.bdTheme('#26a69a', '#26a69a')}; border-radius: 0 2px 2px 0;
background: ${cssManager.bdTheme('hsl(215 70% 50%)', 'hsl(215 70% 60%)')};
} }
.viewTab dees-icon { .viewTab dees-icon {
font-size: 14px; font-size: 16px;
opacity: 0.7; opacity: 0.55;
transition: all 0.15s ease;
} }
.viewTab:hover dees-icon {
opacity: 0.75;
}
.viewTab.selected dees-icon {
opacity: 0.9;
color: ${cssManager.bdTheme('hsl(215 70% 45%)', 'hsl(215 70% 65%)')};
}
.viewTab span {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.appActions { .appActions {
padding: 12px; padding: 12px 8px;
border-top: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')}; border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 13%)')};
} }
.action { .action {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 10px;
padding: 8px; padding: 10px 12px;
border-radius: 4px; border-radius: 8px;
cursor: default; cursor: default;
transition: background 0.1s; transition: all 0.15s ease;
color: ${cssManager.bdTheme('#333', '#ccc')}; color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 65%)')};
} }
.action:hover { .action:hover {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.08)')}; background: ${cssManager.bdTheme('hsl(0 84% 60% / 0.08)', 'hsl(0 84% 60% / 0.12)')};
color: ${cssManager.bdTheme('hsl(0 84% 45%)', 'hsl(0 84% 65%)')};
} }
.action dees-icon { .action dees-icon {
font-size: 14px; font-size: 16px;
opacity: 0.7; opacity: 0.6;
transition: all 0.15s ease;
}
.action:hover dees-icon {
opacity: 0.9;
color: ${cssManager.bdTheme('hsl(0 84% 45%)', 'hsl(0 84% 65%)')};
} }
.appcontent { .appcontent {
@@ -189,7 +268,7 @@ export class DeesSimpleAppDash extends DeesElement {
bottom: 24px; bottom: 24px;
width: calc(100% - 240px); width: calc(100% - 240px);
overflow: auto; overflow: auto;
background: ${cssManager.bdTheme('#f5f5f5', '#000')}; background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(0 0% 5%)')};
overscroll-behavior: contain; overscroll-behavior: contain;
} }
@@ -199,36 +278,51 @@ export class DeesSimpleAppDash extends DeesElement {
bottom: 0px; bottom: 0px;
left: 0px; left: 0px;
width: 100%; width: 100%;
border-top: 1px solid ${cssManager.bdTheme('#00000020', '#ffffff20')};
height: 24px; height: 24px;
background: ${cssManager.bdTheme('#2196f3', '#1565c0')}; background: ${cssManager.bdTheme('hsl(220 13% 18%)', 'hsl(220 13% 12%)')};
z-index: 2; z-index: 2;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
flex-direction: row; flex-direction: row;
font-size: 12px; font-size: 11px;
font-weight: 500;
letter-spacing: 0.01em;
} }
.control { .control {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; gap: 6px;
margin-right: 16px; padding: 0 12px;
height: 100%;
white-space: nowrap; white-space: nowrap;
cursor: default; cursor: default;
opacity: 0.8; color: hsl(0 0% 70%);
transition: opacity 0.2s; transition: all 0.15s ease;
border-left: 1px solid hsl(0 0% 100% / 0.08);
}
.control:first-child {
border-left: none;
} }
.control:hover { .control:hover {
opacity: 1; background: hsl(0 0% 100% / 0.06);
color: hsl(0 0% 95%);
} }
.control dees-icon { .control dees-icon {
font-size: 14px; font-size: 13px;
} }
.control.status-connected dees-icon {
color: hsl(142 70% 50%);
}
.control.status-terminal dees-icon {
color: hsl(45 90% 55%);
}
`, `,
]; ];
@@ -236,12 +330,14 @@ export class DeesSimpleAppDash extends DeesElement {
return html` return html`
<div class="maincontainer"> <div class="maincontainer">
<div class="appbar"> <div class="appbar">
<div>
<div class="sidebar-header"> <div class="sidebar-header">
<dees-icon .icon="${'lucide:grid3x3'}" style="font-size: 18px;"></dees-icon> <div class="header-icon-wrapper">
<dees-icon .icon="${'lucide:layoutGrid'}"></dees-icon>
</div>
<div class="appName">${this.name}</div> <div class="appName">${this.name}</div>
</div> </div>
<div class="viewTabs-container"> <div class="viewTabs-container">
<div class="section-label">Navigation</div>
<div class="viewTabs"> <div class="viewTabs">
${this.viewTabs.map( ${this.viewTabs.map(
(view) => html` (view) => html`
@@ -250,21 +346,22 @@ export class DeesSimpleAppDash extends DeesElement {
@click=${() => this.loadView(view)} @click=${() => this.loadView(view)}
> >
${view.iconName ? html` ${view.iconName ? html`
<dees-icon .icon="${`${view.iconName}`}"></dees-icon> <dees-icon .icon="${view.iconName.includes(':') ? view.iconName : `lucide:${view.iconName}`}"></dees-icon>
` : ''} ` : html`
<span style="flex: 1;">${view.name}</span> <dees-icon .icon="${'lucide:file'}"></dees-icon>
`}
<span>${view.name}</span>
</div> </div>
` `
)} )}
</div> </div>
</div> </div>
</div>
<div class="appActions"> <div class="appActions">
<div class="action" @click=${() => { <div class="action" @click=${() => {
this.dispatchEvent(new CustomEvent('logout', { bubbles: true, composed: true })); this.dispatchEvent(new CustomEvent('logout', { bubbles: true, composed: true }));
}}> }}>
<dees-icon .icon="${'lucide:logOut'}"></dees-icon> <dees-icon .icon="${'lucide:logOut'}"></dees-icon>
<span>Logout</span> <span>Sign out</span>
</div> </div>
</div> </div>
</div> </div>
@@ -272,11 +369,11 @@ export class DeesSimpleAppDash extends DeesElement {
<!-- Content goes here --> <!-- Content goes here -->
</div> </div>
<div class="controlbar"> <div class="controlbar">
<div class="control"> <div class="control status-connected">
<dees-icon .icon="${'lucide:wifi'}"></dees-icon> <dees-icon .icon="${'lucide:wifi'}"></dees-icon>
<span>Connected</span> <span>Connected</span>
</div> </div>
<div class="control" @click=${this.launchTerminal}> <div class="control status-terminal" @click=${this.launchTerminal}>
<dees-icon .icon="${'lucide:terminal'}"></dees-icon> <dees-icon .icon="${'lucide:terminal'}"></dees-icon>
<span>Terminal</span> <span>Terminal</span>
</div> </div>
@@ -315,10 +412,10 @@ export class DeesSimpleAppDash extends DeesElement {
terminal.style.right = '0px'; terminal.style.right = '0px';
terminal.style.bottom = '24px'; terminal.style.bottom = '24px';
terminal.style.opacity = '0'; terminal.style.opacity = '0';
terminal.style.transform = 'translateY(20px)'; terminal.style.transform = 'translateY(8px) scale(0.99)';
terminal.style.transition = 'all 0.2s'; terminal.style.transition = 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)';
terminal.style.background = '#000'; terminal.background = 'hsl(220 13% 8%)';
terminal.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.3)'; terminal.style.boxShadow = '0 25px 50px -12px rgb(0 0 0 / 0.5), 0 0 0 1px rgb(255 255 255 / 0.05)';
terminal.style.maxWidth = `calc(${maincontainer.clientWidth}px -240px)`; terminal.style.maxWidth = `calc(${maincontainer.clientWidth}px -240px)`;
terminal.style.maxHeight = `calc(${maincontainer.clientHeight}px - 24px)`; terminal.style.maxHeight = `calc(${maincontainer.clientHeight}px - 24px)`;
@@ -327,7 +424,7 @@ export class DeesSimpleAppDash extends DeesElement {
await domtools.convenience.smartdelay.delayFor(0); await domtools.convenience.smartdelay.delayFor(0);
terminal.style.opacity = '1'; terminal.style.opacity = '1';
terminal.style.transform = 'translateY(0px)'; terminal.style.transform = 'translateY(0) scale(1)';
return terminal; return terminal;
} }
@@ -335,8 +432,8 @@ export class DeesSimpleAppDash extends DeesElement {
const domtools = await this.domtoolsPromise; const domtools = await this.domtoolsPromise;
if (this.currentTerminal) { if (this.currentTerminal) {
this.currentTerminal.style.opacity = '0'; this.currentTerminal.style.opacity = '0';
this.currentTerminal.style.transform = 'translateY(20px)'; this.currentTerminal.style.transform = 'translateY(8px) scale(0.99)';
await domtools.convenience.smartdelay.delayFor(200); await domtools.convenience.smartdelay.delayFor(250);
this.currentTerminal.remove(); this.currentTerminal.remove();
this.currentTerminal = null; this.currentTerminal = null;
} }

View File

@@ -29,7 +29,7 @@ export class DeesSimpleLogin extends DeesElement {
cssManager.defaultStyles, cssManager.defaultStyles,
css` css`
:host { :host {
color: ${cssManager.bdTheme('#333', '#ccc')}; color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
user-select: none; user-select: none;
display: block; display: block;
width: 100%; width: 100%;
@@ -46,7 +46,7 @@ export class DeesSimpleLogin extends DeesElement {
height: 100%; height: 100%;
top: 0; top: 0;
left: 0; left: 0;
background: ${cssManager.bdTheme('#f5f5f5', '#000')}; background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
} }
.slotContainer { .slotContainer {
@@ -56,43 +56,56 @@ export class DeesSimpleLogin extends DeesElement {
top: 0; top: 0;
left: 0; left: 0;
opacity: 0; opacity: 0;
transition: opacity 0.3s, transform 0.3s; transition: opacity 0.2s ease, transform 0.2s ease;
pointer-events: none; pointer-events: none;
} }
.login { .login {
min-width: 320px; width: 100%;
background: ${cssManager.bdTheme('#ffffff', '#111')}; max-width: 360px;
box-shadow: ${cssManager.bdTheme( display: flex;
'0 4px 12px rgba(0, 0, 0, 0.15)', flex-direction: column;
'0 4px 12px rgba(0, 0, 0, 0.3)' gap: 24px;
)}; }
border-radius: 8px;
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')}; .login-header {
padding: 24px; display: flex;
transition: opacity 0.3s, transform 0.3s; flex-direction: column;
gap: 8px;
text-align: center;
} }
.header { .header {
text-align: center; font-size: 24px;
font-size: 16px;
font-weight: 600; font-weight: 600;
margin-bottom: 20px; letter-spacing: -0.025em;
color: ${cssManager.bdTheme('#000', '#fff')}; color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 98%)')};
} }
.login dees-form { .subheader {
font-size: 14px;
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
}
.login-card {
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
border-radius: 8px;
padding: 24px;
}
.login-card dees-form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 16px;
} }
.login dees-input-text { .login-card dees-input-text {
width: 100%; width: 100%;
} }
.login dees-form-submit { .login-card dees-form-submit {
margin-top: 4px; margin-top: 8px;
width: 100%; width: 100%;
} }
`, `,
@@ -102,14 +115,19 @@ export class DeesSimpleLogin extends DeesElement {
return html` return html`
<div class="loginContainer"> <div class="loginContainer">
<div class="login"> <div class="login">
<div class="header">Login to ${this.name}</div> <div class="login-header">
<div class="header">Sign in</div>
<div class="subheader">Enter your credentials to access ${this.name}</div>
</div>
<div class="login-card">
<dees-form> <dees-form>
<dees-input-text key="username" label="Username" required></dees-input-text> <dees-input-text key="username" label="Username" required></dees-input-text>
<dees-input-text key="password" label="Password" isPasswordBool required></dees-input-text> <dees-input-text key="password" label="Password" isPasswordBool required></dees-input-text>
<dees-form-submit>Login</dees-form-submit> <dees-form-submit>Sign in</dees-form-submit>
</dees-form> </dees-form>
</div> </div>
</div> </div>
</div>
<div class="slotContainer"> <div class="slotContainer">
<slot></slot> <slot></slot>
</div> </div>

View File

@@ -38,6 +38,9 @@ export class DeesTerminal extends DeesElement {
@property() @property()
accessor environment: {[key: string]: string} = {}; accessor environment: {[key: string]: string} = {};
@property()
accessor background: string = '#000000';
// exposing webcontainer // exposing webcontainer
private webcontainerDeferred = new domtools.plugins.smartpromise.Deferred<webcontainer.WebContainer>(); private webcontainerDeferred = new domtools.plugins.smartpromise.Deferred<webcontainer.WebContainer>();
public webcontainerPromise = this.webcontainerDeferred.promise; public webcontainerPromise = this.webcontainerDeferred.promise;
@@ -58,7 +61,7 @@ export class DeesTerminal extends DeesElement {
css` css`
:host { :host {
padding: 20px; padding: 20px;
background: #000; background: var(--dees-terminal-background, #000000);
position: absolute; position: absolute;
height: 100%; height: 100%;
width: 100%; width: 100%;
@@ -153,7 +156,7 @@ export class DeesTerminal extends DeesElement {
.xterm .composition-view { .xterm .composition-view {
/* TODO: Composition position got messed up somewhere */ /* TODO: Composition position got messed up somewhere */
background: #000; background: var(--dees-terminal-background, #000000);
color: #fff; color: #fff;
display: none; display: none;
position: absolute; position: absolute;
@@ -167,7 +170,7 @@ export class DeesTerminal extends DeesElement {
.xterm .xterm-viewport { .xterm .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */ /* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: #000; background-color: var(--dees-terminal-background, #000000);
overflow-y: scroll; overflow-y: scroll;
cursor: default; cursor: default;
position: absolute; position: absolute;
@@ -261,11 +264,18 @@ export class DeesTerminal extends DeesElement {
): Promise<void> { ): Promise<void> {
const domtools = await this.domtoolsPromise; const domtools = await this.domtoolsPromise;
super.firstUpdated(_changedProperties); super.firstUpdated(_changedProperties);
// Sync CSS variable with background property
this.style.setProperty('--dees-terminal-background', this.background);
const container = this.shadowRoot.getElementById('container'); const container = this.shadowRoot.getElementById('container');
const term = new Terminal({ const term = new Terminal({
convertEol: true, convertEol: true,
cursorBlink: true, cursorBlink: true,
theme: {
background: this.background,
},
}); });
this.fitAddon = new FitAddon(); this.fitAddon = new FitAddon();
term.loadAddon(this.fitAddon); term.loadAddon(this.fitAddon);

View File

@@ -0,0 +1,189 @@
import type { TemplateResult } from '@design.estate/dees-element';
import type { IAppBarMenuItem } from './appbarmenuitem.js';
import type { ITab } from './tab.js';
import type { ISecondaryMenuGroup } from './secondarymenu.js';
/**
* User configuration for the app bar
*/
export interface IAppUser {
name: string;
email?: string;
avatar?: string;
status?: 'online' | 'offline' | 'busy' | 'away';
}
/**
* View definition for the view registry
*/
export interface IViewDefinition {
/** Unique identifier for routing */
id: string;
/** Display name */
name: string;
/** Optional icon */
iconName?: string;
/** The view content - can be a tag name, element class, or template function */
content: string | (new () => HTMLElement) | (() => TemplateResult);
/** Secondary menu items specific to this view */
secondaryMenu?: ISecondaryMenuGroup[];
/** Content tabs specific to this view */
contentTabs?: ITab[];
/** Optional route path (defaults to id) */
route?: string;
/** Badge to show on menu item */
badge?: string | number;
badgeVariant?: 'default' | 'success' | 'warning' | 'error';
}
/**
* Main menu section with view references
*/
export interface IMainMenuSection {
/** Section name (optional for ungrouped) */
name?: string;
/** Views in this section (by ID reference) */
views: string[];
}
/**
* Main menu configuration
*/
export interface IMainMenuConfig {
/** Menu sections with view references */
sections?: IMainMenuSection[];
/** Bottom pinned items (view IDs) */
bottomItems?: string[];
}
/**
* App bar configuration
*/
export interface IAppBarConfig {
menuItems?: IAppBarMenuItem[];
breadcrumbs?: string;
breadcrumbSeparator?: string;
showWindowControls?: boolean;
showSearch?: boolean;
user?: IAppUser;
profileMenuItems?: IAppBarMenuItem[];
}
/**
* Branding configuration
*/
export interface IBrandingConfig {
logoIcon?: string;
logoText?: string;
}
/**
* Routing configuration
*/
export interface IRoutingConfig {
/** Routing mode */
mode: 'hash' | 'history' | 'external' | 'none';
/** Base path for history mode */
basePath?: string;
/** Default view ID to show on startup */
defaultView?: string;
/** Sync URL on view change */
syncUrl?: boolean;
/** Handle 404s - show view ID or callback */
notFound?: string | (() => void);
}
/**
* State persistence configuration
*/
export interface IStatePersistenceConfig {
/** Enable state persistence */
enabled: boolean;
/** Storage key prefix */
storageKey?: string;
/** Storage type */
storage?: 'localStorage' | 'sessionStorage' | 'memory';
/** What to persist */
persist?: {
mainMenuCollapsed?: boolean;
secondaryMenuCollapsed?: boolean;
selectedView?: boolean;
secondaryMenuSelection?: boolean;
collapsedGroups?: boolean;
};
}
/**
* Activity log configuration
*/
export interface IActivityLogConfig {
enabled?: boolean;
width?: number;
}
/**
* Main unified configuration interface for dees-appui-base
*/
export interface IAppConfig {
/** Application branding */
branding?: IBrandingConfig;
/** App bar configuration */
appBar?: IAppBarConfig;
/** View definitions (the registry) */
views: IViewDefinition[];
/** Main menu structure */
mainMenu?: IMainMenuConfig;
/** Routing configuration */
routing?: IRoutingConfig;
/** State persistence configuration */
statePersistence?: IStatePersistenceConfig;
/** Activity log configuration */
activityLog?: IActivityLogConfig;
/** Event callbacks (optional shorthand) */
onViewChange?: (viewId: string, view: IViewDefinition) => void;
onSearch?: () => void;
}
/**
* Serialized UI state for persistence
*/
export interface IAppUIState {
/** Current view ID */
currentViewId?: string;
/** Main menu collapsed state */
mainMenuCollapsed?: boolean;
/** Secondary menu collapsed state */
secondaryMenuCollapsed?: boolean;
/** Selected secondary menu item key */
secondaryMenuSelectedKey?: string;
/** Collapsed group names in secondary menu */
collapsedGroups?: string[];
/** Timestamp of last save */
timestamp?: number;
}
/**
* Route change event detail
*/
export interface IRouteChangeEvent {
viewId: string;
previousViewId: string | null;
params?: Record<string, string>;
source: 'navigation' | 'popstate' | 'initial' | 'programmatic';
}
/**
* View change event detail
*/
export interface IViewChangeEvent {
viewId: string;
view: IViewDefinition;
previousView?: IViewDefinition;
}

View File

@@ -3,3 +3,4 @@ export * from './selectionoption.js';
export * from './appbarmenuitem.js'; export * from './appbarmenuitem.js';
export * from './menugroup.js'; export * from './menugroup.js';
export * from './secondarymenu.js'; export * from './secondarymenu.js';
export * from './appconfig.js';