@operato/menu 8.0.0-beta.0 → 8.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,131 +0,0 @@
1
- import '@material/web/icon/icon.js'
2
- import './ox-menu-portrait'
3
- import './ox-menu-landscape'
4
-
5
- import { css, html, LitElement, PropertyValues } from 'lit'
6
- import { customElement, property, query, state } from 'lit/decorators.js'
7
- import { connect } from 'pwa-helpers'
8
-
9
- import { store } from '@operato/shell'
10
- import { ScrollbarStyles } from '@operato/styles'
11
-
12
- import { Menu } from './types'
13
-
14
- function isActiveMenu(menu: Menu, path: string) {
15
- return (
16
- menu.path?.split('?')[0] === path ||
17
- (menu.active && typeof menu.active === 'function' && menu.active.call(menu, { path }))
18
- )
19
- }
20
-
21
- @customElement('ox-menu-part')
22
- export class OxMenuPart extends connect(store)(LitElement) {
23
- static styles = [
24
- ScrollbarStyles,
25
- css`
26
- :host {
27
- display: flex;
28
- overflow-y: auto;
29
- flex-direction: column;
30
- height: 100%;
31
- min-width: 200px;
32
- background-color: var(--md-sys-color-surface);
33
- }
34
-
35
- :host([landscape]) {
36
- overflow-x: auto;
37
- flex-direction: row;
38
- width: 100%;
39
- min-height: 20px;
40
- }
41
-
42
- ox-menu-portrait,
43
- ox-menu-landscape {
44
- flex: 1;
45
- }
46
- `
47
- ]
48
-
49
- @property({ type: String }) page!: string
50
- @property({ type: String }) resourceId?: string
51
- @property({ type: Array }) menus?: Menu[]
52
- @property({ type: String }) orientation?: 'landscape' | 'portrait'
53
-
54
- @state() slotTemplate: any
55
- @state() _activeTopLevel?: Menu
56
- @state() _activeMenu?: Menu
57
- @state() _path?: string
58
-
59
- render() {
60
- return html`
61
- <slot name="head"></slot>
62
- ${this.orientation !== 'landscape'
63
- ? html`<ox-menu-portrait
64
- .menus=${this.menus}
65
- .activeTopLevel=${this._activeTopLevel}
66
- .activeMenu=${this._activeMenu}
67
- .path=${this._path}
68
- ></ox-menu-portrait>`
69
- : html`<ox-menu-landscape
70
- .menus=${this.menus}
71
- .activeTopLevel=${this._activeTopLevel}
72
- .activeMenu=${this._activeMenu}
73
- .path=${this._path}
74
- ></ox-menu-landscape>`}
75
- <slot name="tail"></slot>
76
- `
77
- }
78
-
79
- firstUpdated() {
80
- this.renderRoot.addEventListener('active-toplevel', (e: Event) => {
81
- e.stopPropagation()
82
- e.preventDefault()
83
-
84
- this._activeTopLevel = (e as CustomEvent).detail
85
- })
86
- }
87
-
88
- updated(changes: PropertyValues<this>) {
89
- if (changes.has('menus') || changes.has('page') || changes.has('resourceId')) {
90
- this.findActivePage()
91
- }
92
-
93
- if (changes.has('orientation')) {
94
- if (this.orientation == 'portrait') {
95
- this.removeAttribute('landscape')
96
- } else {
97
- this.setAttribute('landscape', '')
98
- }
99
- }
100
-
101
- if (changes.has('slotTemplate')) {
102
- this.replaceChild(this.slotTemplate, this.renderRoot)
103
- }
104
- }
105
-
106
- stateChanged(state: any): void {
107
- this.page = state.route.page
108
- this.resourceId = state.route.resourceId
109
- this.menus = state.liteMenu.menus || []
110
- this.slotTemplate = state.liteMenu.slotTemplate
111
- }
112
-
113
- private findActivePage() {
114
- var path = this.resourceId ? `${this.page}/${this.resourceId}` : this.page
115
- var menus = this.menus || []
116
- var activeMenu
117
-
118
- this._activeTopLevel = menus.find(menu => {
119
- if (isActiveMenu(menu, path)) {
120
- activeMenu = menu
121
- return true
122
- } else if (menu.menus) {
123
- activeMenu = menu.menus.find(menu => isActiveMenu(menu, path))
124
- return !!activeMenu
125
- }
126
- })
127
-
128
- this._path = path
129
- this._activeMenu = activeMenu || this._activeTopLevel
130
- }
131
- }
@@ -1,91 +0,0 @@
1
- import '@material/web/icon/icon.js'
2
-
3
- import { html, LitElement, nothing, TemplateResult } from 'lit'
4
- import { customElement, property } from 'lit/decorators.js'
5
-
6
- import { navigate } from '@operato/shell'
7
- import { ScrollbarStyles } from '@operato/styles'
8
-
9
- import { Menu } from './types'
10
- import { MenuPortraitStyles } from './menu-portrait-styles'
11
-
12
- @customElement('ox-menu-portrait')
13
- export class OxMenuPortrait extends LitElement {
14
- static styles = [ScrollbarStyles, MenuPortraitStyles]
15
-
16
- @property({ type: Array }) menus?: Menu[]
17
- @property({ type: Object }) activeTopLevel?: Menu
18
- @property({ type: Object }) activeMenu?: Menu
19
- @property({ type: String }) path!: string
20
-
21
- renderMenus(menus: Menu[], activeTopLevel?: Menu, activeMenu?: Menu): TemplateResult | typeof nothing {
22
- if (menus.length == 0) {
23
- return nothing
24
- }
25
-
26
- return html`
27
- <ul>
28
- ${menus.map(menu => {
29
- var { type, name, active, path, label = name, badge, icon, menus = [] } = menu
30
- active = active && typeof active === 'function' ? active.call(menu, { path: this.path }) : false
31
- badge = typeof badge === 'function' ? badge.call(menu) : badge ?? false
32
-
33
- return type == 'group'
34
- ? html`<li group-label>${label}</li>`
35
- : html`
36
- <li ?active=${activeTopLevel ? menu === activeTopLevel : active} .menu=${menu} menu>
37
- <a href=${path || '#'}>
38
- <md-icon filled>${icon || 'fiber_manual_record'}</md-icon>
39
- <span>${label}</span>${badge !== false ? html`<div badge>${badge}</div>` : html``}
40
- ${menus.length > 0 ? html` <md-icon filled submenu-button></md-icon> ` : html``}
41
- </a>
42
- ${menus && this.renderMenus(menus || [], activeMenu)}
43
- </li>
44
- `
45
- })}
46
- </ul>
47
- `
48
- }
49
-
50
- render() {
51
- const { menus, activeTopLevel, activeMenu } = this
52
- return this.renderMenus(menus || [], activeTopLevel, activeMenu)
53
- }
54
-
55
- firstUpdated() {
56
- this.renderRoot.addEventListener('click', (e: Event) => {
57
- const menuElement = (e.target as Element)!.closest('[menu]')
58
-
59
- //@ts-ignore
60
- if (menuElement?.menu) {
61
- //@ts-ignore
62
- let menu = menuElement.menu
63
-
64
- if (!menu.path) {
65
- /* protect to act move to href. */
66
- e.stopPropagation()
67
- e.preventDefault()
68
- }
69
-
70
- this.dispatchEvent(
71
- new CustomEvent('active-toplevel', {
72
- bubbles: true,
73
- detail: this.activeTopLevel === menu ? undefined : menu
74
- })
75
- )
76
- }
77
-
78
- /* to respond even if current acting menu is selected */
79
- let href = (e.target as HTMLAnchorElement)!.href
80
- href && location.href === href && navigate(href + '#force', true)
81
- })
82
-
83
- /* to hide scrollbar during transition */
84
- this.renderRoot.addEventListener('transitionstart', e => {
85
- ;(e.target as Element).removeAttribute('settled')
86
- })
87
- this.renderRoot.addEventListener('transitionend', e => {
88
- ;(e.target as Element).setAttribute('settled', '')
89
- })
90
- }
91
- }
@@ -1,147 +0,0 @@
1
- import '@material/web/icon/icon.js'
2
-
3
- import { css, html, LitElement, PropertyValueMap, TemplateResult } from 'lit'
4
- import { customElement, property, query, state } from 'lit/decorators.js'
5
- import { connect } from 'pwa-helpers'
6
-
7
- import { toggleOverlay } from '@operato/layout'
8
- import { store } from '@operato/shell'
9
-
10
- import { Menu } from './types'
11
-
12
- @customElement('ox-top-menu-bar')
13
- export class OxTopMenuBar extends connect(store)(LitElement) {
14
- static styles = [
15
- css`
16
- :host {
17
- display: flex;
18
- flex-direction: row;
19
- }
20
-
21
- span {
22
- flex: 1;
23
- }
24
-
25
- ul {
26
- display: flex;
27
- align-items: center;
28
- list-style: none;
29
- margin: 0;
30
- padding: 0;
31
- }
32
-
33
- li {
34
- display: inline-flex;
35
- flex-direction: row nowrap;
36
- float: left;
37
- overflow: none;
38
- }
39
-
40
- a {
41
- display: inline-block;
42
- white-space: nowrap;
43
- overflow: hidden;
44
- text-overflow: ellipsis;
45
- padding: var(--padding-default) var(--spacing-large) var(--spacing-small) var(--spacing-large);
46
- text-decoration: none;
47
- color: white;
48
- }
49
- a * {
50
- vertical-align: middle;
51
- }
52
- a md-icon {
53
- opacity: 0.5;
54
- position: relative;
55
- top: -2px;
56
- font-size: 1em;
57
- }
58
-
59
- li[active] a {
60
- font-weight: bold;
61
- }
62
- li[active] a md-icon {
63
- opacity: 1;
64
- }
65
- `
66
- ]
67
-
68
- @property({ type: String }) page?: string
69
- @property({ type: String }) resourceId?: string
70
- @property({ type: Array }) menus?: Menu[]
71
- @property({ type: Object }) slotTemplate!: Node
72
-
73
- @state() private _activeTopLevel?: Menu
74
-
75
- render() {
76
- const { menus = [], _activeTopLevel } = this
77
-
78
- return html`
79
- <slot name="head"></slot>
80
- <ul>
81
- ${menus.map(menu =>
82
- menu.type == 'group'
83
- ? html``
84
- : html`
85
- <li ?active=${menu === _activeTopLevel}>
86
- <a
87
- href="#"
88
- @click=${(e: MouseEvent) => {
89
- e.preventDefault()
90
- toggleOverlay('ox-menu-part', {
91
- backdrop: true
92
- })
93
- }}
94
- >
95
- ${menu.name}
96
- <md-icon>expand_more</md-icon>
97
- </a>
98
- </li>
99
- `
100
- )}
101
- </ul>
102
- <slot name="tail"></slot>
103
- `
104
- }
105
-
106
- stateChanged(state: any): void {
107
- this.page = state.route.page
108
- this.resourceId = state.route.resourceId
109
- this.menus = state.liteMenu.menus || []
110
- this.slotTemplate = state.liteMenu.slotTemplate
111
- }
112
-
113
- // firstUpdated() {
114
- // this.addEventListener('mousewheel', this.onWheelEvent.bind(this), false)
115
- // }
116
-
117
- updated(changes: PropertyValueMap<this>) {
118
- if (changes.has('menus') || changes.has('page') || changes.has('resourceId')) {
119
- this._findActivePage()
120
- }
121
-
122
- if (changes.has('slotTemplate')) {
123
- this.replaceChild(this.slotTemplate, this.renderRoot)
124
- }
125
- }
126
-
127
- // onWheelEvent(e) {
128
- // var delta = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail))
129
- // this.scrollLeft -= delta * 40
130
- // console.log('delta', this.scrollLeft, delta, e.wheelDelta || -e.detail)
131
-
132
- // e.preventDefault()
133
- // }
134
-
135
- _findActivePage() {
136
- var path = this.resourceId ? `${this.page}/${this.resourceId}` : this.page
137
- var menus = this.menus || []
138
-
139
- this._activeTopLevel = menus.find(menu => {
140
- if (menu.path?.split('?')[0] === path) {
141
- return true
142
- } else if (menu.menus) {
143
- return !!menu.menus.find(menu => menu.path?.split('?')[0] === path)
144
- }
145
- })
146
- }
147
- }
package/src/types.ts DELETED
@@ -1,11 +0,0 @@
1
- export type Menu = {
2
- type?: 'group' | 'board'
3
- name?: string
4
- description?: string
5
- path?: string
6
- icon?: string
7
- label?: string
8
- badge?: boolean | string | (() => boolean)
9
- active?: boolean | ((menu?: Menu) => boolean)
10
- menus?: Menu[]
11
- }
@@ -1,97 +0,0 @@
1
- import '@material/web/icon/icon.js'
2
- import '../src/ox-menu-portrait'
3
-
4
- import { css, html, LitElement, PropertyValues } from 'lit'
5
- import { customElement, property, query, state } from 'lit/decorators.js'
6
-
7
- import { ScrollbarStyles } from '@operato/styles'
8
-
9
- import { Menu } from '../src/types'
10
-
11
- function isActiveMenu(menu: Menu, path?: string) {
12
- return (
13
- menu.path?.split('?')[0] === path ||
14
- (menu.active && typeof menu.active === 'function' && menu.active.call(menu, { path }))
15
- )
16
- }
17
-
18
- @customElement('ox-menu-container')
19
- export class OxMenuContainer extends LitElement {
20
- static styles = [
21
- ScrollbarStyles,
22
- css`
23
- :host {
24
- display: flex;
25
- overflow-y: auto;
26
- flex-direction: column;
27
- background-color: var(--md-sys-color-surface);
28
- }
29
- `
30
- ]
31
-
32
- @property({ type: Array }) menus?: Menu[]
33
-
34
- @state() activeMenu?: Menu
35
- @state() activeTopLevel?: Menu
36
- @state() path?: string
37
-
38
- render() {
39
- return html`
40
- <div
41
- @click=${(e: MouseEvent) => {
42
- if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey) {
43
- return
44
- }
45
- const anchor: HTMLAnchorElement = e.composedPath().filter((n: any) => n.tagName === 'A')[0] as any
46
- if (!anchor) {
47
- return
48
- }
49
- const href = anchor.href
50
- e.preventDefault()
51
-
52
- var [_, path, ...others] = new URL(href).pathname.split('/')
53
- this.path = path
54
- }}
55
- >
56
- <ox-menu-portrait
57
- .menus=${this.menus}
58
- .activeTopLevel=${this.activeTopLevel}
59
- .activeMenu=${this.activeMenu}
60
- .path=${this.path || ''}
61
- ></ox-menu-portrait>
62
- </div>
63
- `
64
- }
65
-
66
- firstUpdated() {
67
- this.renderRoot.addEventListener('active-toplevel', (e: Event) => {
68
- e.stopPropagation()
69
- e.preventDefault()
70
-
71
- this.activeTopLevel = (e as CustomEvent).detail
72
- })
73
- }
74
-
75
- updated(changes: PropertyValues<this>) {
76
- if (changes.has('menus') || changes.has('path')) {
77
- this.findActivePage()
78
- }
79
- }
80
-
81
- private findActivePage() {
82
- var menus = this.menus || []
83
- var activeMenu
84
-
85
- this.activeTopLevel = menus.find(menu => {
86
- if (isActiveMenu(menu, this.path)) {
87
- activeMenu = menu
88
- return true
89
- } else if (menu.menus) {
90
- activeMenu = menu.menus.find((menu: Menu) => isActiveMenu(menu, this.path))
91
- return !!activeMenu
92
- }
93
- })
94
-
95
- this.activeMenu = activeMenu || this.activeTopLevel
96
- }
97
- }
@@ -1,65 +0,0 @@
1
- import '../src/ox-menu-portrait'
2
-
3
- import { html, TemplateResult } from 'lit'
4
- import { menus } from './test-menus'
5
- import './ox-menu-container'
6
-
7
- export default {
8
- title: 'OxMenuPortrait',
9
- component: 'ox-menu-portrait',
10
- argTypes: {}
11
- }
12
-
13
- interface Story<T> {
14
- (args: T): TemplateResult
15
- args?: Partial<T>
16
- argTypes?: Record<string, unknown>
17
- }
18
-
19
- interface ArgTypes {}
20
-
21
- const Template: Story<ArgTypes> = ({}: ArgTypes) => html`
22
- <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet" />
23
-
24
- <link
25
- href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL@20..48,100..700,0..1"
26
- rel="stylesheet"
27
- />
28
- <link
29
- href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL@20..48,100..700,0..1"
30
- rel="stylesheet"
31
- />
32
- <link
33
- href="https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp:opsz,wght,FILL@20..48,100..700,0..1"
34
- rel="stylesheet"
35
- />
36
-
37
- <link href="/themes/app-theme.css" rel="stylesheet" />
38
- <link href="/themes/light.css" rel="stylesheet" />
39
- <link href="/themes/dark.css" rel="stylesheet" />
40
- <link href="/themes/spacing.css" rel="stylesheet" />
41
- <link href="/themes/grist-theme.css" rel="stylesheet" />
42
- <link href="/themes/form-theme.css" rel="stylesheet" />
43
-
44
- <style>
45
- body {
46
- background-color: cyan;
47
- }
48
-
49
- .container {
50
- display: flex;
51
- width: 260px;
52
- height: 500px;
53
- }
54
-
55
- ox-menu-container {
56
- flex: 1;
57
- }
58
- </style>
59
-
60
- <div class="container">
61
- <ox-menu-container .menus=${menus}></ox-menu-container>
62
- </div>
63
- `
64
-
65
- export const Regular = Template.bind({})