@operato/context 1.1.19

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.
Files changed (35) hide show
  1. package/.editorconfig +29 -0
  2. package/.storybook/main.js +3 -0
  3. package/.storybook/server.mjs +8 -0
  4. package/CHANGELOG.md +11 -0
  5. package/README.md +75 -0
  6. package/demo/index.html +32 -0
  7. package/dist/src/index.d.ts +1 -0
  8. package/dist/src/index.js +2 -0
  9. package/dist/src/index.js.map +1 -0
  10. package/dist/src/layouts/ox-context-toolbar.d.ts +17 -0
  11. package/dist/src/layouts/ox-context-toolbar.js +127 -0
  12. package/dist/src/layouts/ox-context-toolbar.js.map +1 -0
  13. package/dist/src/styles/ox-context-toolbar-overlay-style.d.ts +1 -0
  14. package/dist/src/styles/ox-context-toolbar-overlay-style.js +58 -0
  15. package/dist/src/styles/ox-context-toolbar-overlay-style.js.map +1 -0
  16. package/dist/src/tools/ox-page-action-context-bar.d.ts +27 -0
  17. package/dist/src/tools/ox-page-action-context-bar.js +172 -0
  18. package/dist/src/tools/ox-page-action-context-bar.js.map +1 -0
  19. package/dist/src/tools/ox-page-action-overlay-template.d.ts +16 -0
  20. package/dist/src/tools/ox-page-action-overlay-template.js +116 -0
  21. package/dist/src/tools/ox-page-action-overlay-template.js.map +1 -0
  22. package/dist/src/tools/ox-title-bar.d.ts +17 -0
  23. package/dist/src/tools/ox-title-bar.js +99 -0
  24. package/dist/src/tools/ox-title-bar.js.map +1 -0
  25. package/dist/tsconfig.tsbuildinfo +1 -0
  26. package/package.json +101 -0
  27. package/src/index.ts +1 -0
  28. package/src/layouts/ox-context-toolbar.ts +125 -0
  29. package/src/styles/ox-context-toolbar-overlay-style.ts +58 -0
  30. package/src/tools/ox-page-action-context-bar.ts +176 -0
  31. package/src/tools/ox-page-action-overlay-template.ts +112 -0
  32. package/src/tools/ox-title-bar.ts +99 -0
  33. package/tsconfig.json +23 -0
  34. package/web-dev-server.config.mjs +27 -0
  35. package/web-test-runner.config.mjs +41 -0
@@ -0,0 +1,58 @@
1
+ import { css } from 'lit'
2
+
3
+ export const ContextToolbarOverlayStyle = css`
4
+ :host {
5
+ display: block;
6
+
7
+ background-color: var(--context-ui-background-color);
8
+ box-shadow: var(--context-ui-box-shadow);
9
+ padding: var(--context-ui-padding);
10
+ }
11
+
12
+ ul {
13
+ margin: 0;
14
+ padding: 0;
15
+ list-style: none;
16
+ overflow-y: auto;
17
+ }
18
+
19
+ li {
20
+ display: flex;
21
+
22
+ border-bottom: var(--context-ui-list-border-bottom);
23
+ padding: var(--context-ui-list-padding);
24
+ }
25
+
26
+ li > mwc-icon {
27
+ font-size: 1em;
28
+
29
+ padding: var(--context-ui-padding);
30
+ color: var(--context-ui-list-color);
31
+ }
32
+
33
+ li > span {
34
+ margin: auto 0;
35
+ flex: 1;
36
+
37
+ color: var(--context-ui-list-color);
38
+ }
39
+
40
+ li:hover mwc-icon,
41
+ li:hover span {
42
+ color: #fff;
43
+ }
44
+
45
+ li:hover {
46
+ cursor: pointer;
47
+
48
+ border-bottom: var(--context-ui-list-border-hover-bottom);
49
+ }
50
+
51
+ @media (max-width: 460px) {
52
+ :host {
53
+ min-width: 100%;
54
+ box-shadow: none;
55
+ border-radius: 0;
56
+ }
57
+ }
58
+ `
@@ -0,0 +1,176 @@
1
+ import '@material/mwc-button'
2
+ import '@material/mwc-icon'
3
+ import './ox-page-action-overlay-template.js'
4
+
5
+ import { css, html, LitElement, PropertyValues } from 'lit'
6
+ import { customElement, property, query, state } from 'lit/decorators.js'
7
+ import throttle from 'lodash-es/throttle'
8
+ import { connect } from 'pwa-helpers/connect-mixin'
9
+
10
+ import { openOverlay } from '@operato/layout'
11
+ import { store } from '@operato/shell'
12
+ import { sleep } from '@operato/utils'
13
+
14
+ @customElement('ox-page-action-context-bar')
15
+ export class PageActionContextBar extends connect(store)(LitElement) {
16
+ static get styles() {
17
+ return [
18
+ css`
19
+ :host {
20
+ display: flex;
21
+ overflow: hidden;
22
+ position: relative;
23
+ }
24
+
25
+ :host *:focus {
26
+ outline: none;
27
+ }
28
+
29
+ mwc-button {
30
+ white-space: nowrap;
31
+ margin: var(--context-action-bar-button-margin);
32
+ border-radius: var(--context-action-bar-more-button-border-radius);
33
+ }
34
+
35
+ [danger] {
36
+ --mdc-theme-primary: var(--mdc-danger-button-primary-color);
37
+ --mdc-button-outline-color: var(--mdc-danger-button-outline-color);
38
+ }
39
+
40
+ [more] {
41
+ position: absolute;
42
+ right: 0;
43
+ background-color: var(--context-action-bar-more-button-background-color);
44
+ box-shadow: -2px 0px 2px 1px rgba(0, 0, 0, 0.2);
45
+ padding: var(--context-action-bar-more-button-padding);
46
+ color: var(--context-action-bar-more-button-color);
47
+ }
48
+
49
+ @media screen and (max-width: 460px) {
50
+ mwc-button {
51
+ --mdc-button-horizontal-padding: var(--padding-default);
52
+ --mdc-shape-small: var(--context-action-bar-more-button-border-radius);
53
+ }
54
+ [more] {
55
+ position: fixed;
56
+ margin-top: -10px;
57
+ -webkit-border-top-left-radius: 10px;
58
+ -moz-border-radius-topleft: 10px;
59
+ border-top-left-radius: 10px;
60
+ padding: var(--padding-default);
61
+ font-size: 2rem;
62
+ }
63
+ }
64
+
65
+ @keyframes rotate {
66
+ from {
67
+ transform: rotate(0deg);
68
+ }
69
+ to {
70
+ transform: rotate(360deg);
71
+ }
72
+ }
73
+ `
74
+ ]
75
+ }
76
+
77
+ @state() actions: any[] = []
78
+ @state() private overflown: boolean = false
79
+
80
+ private overflownUpdaterThrottle: any
81
+ private resizeCallback: any
82
+
83
+ get overflownUpdater() {
84
+ if (!this.overflownUpdaterThrottle) {
85
+ this.overflownUpdaterThrottle = throttle(() => {
86
+ this.overflown = this.scrollWidth > this.clientWidth
87
+ }, 100)
88
+ }
89
+
90
+ return this.overflownUpdaterThrottle
91
+ }
92
+
93
+ connectedCallback() {
94
+ super.connectedCallback()
95
+
96
+ this.resizeCallback = () => this.overflownUpdater()
97
+
98
+ window.addEventListener('resize', this.resizeCallback)
99
+ }
100
+
101
+ disconnectedCallback() {
102
+ super.disconnectedCallback()
103
+
104
+ window.removeEventListener('resize', this.resizeCallback)
105
+ }
106
+
107
+ render() {
108
+ return html`
109
+ ${this.renderActions()}
110
+ ${this.overflown ? html` <mwc-icon more @click=${this.showMore.bind(this)}>more_horiz</mwc-icon> ` : html``}
111
+ `
112
+ }
113
+
114
+ renderActions() {
115
+ return html`
116
+ ${this.actions.map(($action, idx) => {
117
+ let { type, title, icon, action, select, href, emphasis } = $action as any
118
+ let { outlined, raised, dense, danger } = emphasis || {}
119
+
120
+ return type == 'text'
121
+ ? html` <span>${title}</span> `
122
+ : type == 'select' || (select && select.length > 0)
123
+ ? html`
124
+ <select @change=${action}>
125
+ ${select.map((option: any) => html` <option>${option}</option> `)}
126
+ </select>
127
+ `
128
+ : type == 'link'
129
+ ? html` <a href=${href}>${title}</a> `
130
+ : html`
131
+ <mwc-button
132
+ label=${title}
133
+ icon=${icon || 'done_all'}
134
+ ?dense=${dense}
135
+ ?raised=${raised}
136
+ ?outlined=${outlined}
137
+ ?danger=${danger}
138
+ @click=${async (e: MouseEvent) => {
139
+ /* all actions should be bloked for a second */
140
+ const button = e.currentTarget as any
141
+ button.icon = 'rotate_right'
142
+ button.setAttribute('working', true)
143
+ button.disabled = true
144
+
145
+ try {
146
+ await action()
147
+ } finally {
148
+ await sleep(1000)
149
+ button.disabled = false
150
+ /* because icon can be changed after sleep, don't use closure 'icon'. */
151
+ button.icon = this.actions[idx]?.icon || 'done_all'
152
+ button.removeAttribute('working')
153
+ }
154
+ }}
155
+ ></mwc-button>
156
+ `
157
+ })}
158
+ `
159
+ }
160
+
161
+ async updated(changed: PropertyValues<this>) {
162
+ if (changed.has('actions')) {
163
+ requestAnimationFrame(this.overflownUpdater)
164
+ }
165
+ }
166
+
167
+ stateChanged(state: any) {
168
+ this.actions = state.route?.context?.actions?.filter((action: any) => !!action) || []
169
+ }
170
+
171
+ showMore() {
172
+ openOverlay('context-toolbar-overlay', {
173
+ template: html`<ox-page-action-overlay-template></ox-page-action-overlay-template>`
174
+ })
175
+ }
176
+ }
@@ -0,0 +1,112 @@
1
+ import '@material/mwc-icon'
2
+
3
+ import { css, html, LitElement } from 'lit-element'
4
+ import { customElement, property, query, state } from 'lit/decorators.js'
5
+ import { connect } from 'pwa-helpers'
6
+
7
+ import { store } from '@operato/shell'
8
+ import { ScrollbarStyles } from '@operato/styles'
9
+ import { sleep } from '@operato/utils'
10
+
11
+ import { ContextToolbarOverlayStyle } from '../styles/ox-context-toolbar-overlay-style.js'
12
+
13
+ @customElement('ox-page-action-overlay-template')
14
+ export class PageActionOverlayTemplate extends connect(store)(LitElement) {
15
+ static get styles() {
16
+ return [
17
+ ScrollbarStyles,
18
+ ContextToolbarOverlayStyle,
19
+ css`
20
+ :host {
21
+ display: flex;
22
+ flex-wrap: wrap;
23
+ gap: var(--margin-narrow);
24
+ }
25
+ :host *:focus {
26
+ outline: none;
27
+ }
28
+
29
+ mwc-button {
30
+ background-color: var(--main-section-background-color);
31
+ border-radius: var(--context-action-bar-more-button-border-radius);
32
+ }
33
+
34
+ [danger] {
35
+ --mdc-theme-primary: var(--mdc-danger-button-primary-color);
36
+ --mdc-button-outline-color: var(--mdc-danger-button-outline-color);
37
+ }
38
+
39
+ @media screen and (max-width: 460px) {
40
+ mwc-button {
41
+ --mdc-button-horizontal-padding: var(--padding-default);
42
+ border-radius: 4px;
43
+ }
44
+ }
45
+
46
+ @keyframes rotate {
47
+ from {
48
+ transform: rotate(0deg);
49
+ }
50
+ to {
51
+ transform: rotate(360deg);
52
+ }
53
+ }
54
+ `
55
+ ]
56
+ }
57
+
58
+ @state() private actions: any[] = []
59
+
60
+ render() {
61
+ return html`
62
+ ${this.actions.map(($action, idx) => {
63
+ let { type, title, icon, action, select, href, emphasis } = $action as any
64
+ let { outlined, raised, dense, danger } = emphasis || {}
65
+
66
+ return type == 'text'
67
+ ? html` <span>${title}</span> `
68
+ : type == 'select' || (select && select.length > 0)
69
+ ? html`
70
+ <select @change=${action}>
71
+ ${select.map((option: any) => html` <option>${option}</option> `)}
72
+ </select>
73
+ `
74
+ : type == 'link'
75
+ ? html` <a href=${href}>${title}</a> `
76
+ : html`
77
+ <mwc-button
78
+ label=${title}
79
+ icon=${icon || 'done_all'}
80
+ ?dense=${dense}
81
+ ?raised=${raised}
82
+ ?outlined=${outlined}
83
+ ?danger=${danger}
84
+ @click=${async (e: MouseEvent) => {
85
+ /* all actions should be bloked for a second */
86
+ const button = e.currentTarget as any
87
+ button.icon = 'rotate_right'
88
+ button.setAttribute('working', true)
89
+ button.disabled = true
90
+
91
+ try {
92
+ action()
93
+ } finally {
94
+ await sleep(1000)
95
+ button.disabled = false
96
+ /* because icon can be changed after sleep, don't use closure 'icon'. */
97
+ button.icon = this.actions[idx]?.icon || 'done_all'
98
+ button.removeAttribute('working')
99
+
100
+ window.history.back()
101
+ }
102
+ }}
103
+ ></mwc-button>
104
+ `
105
+ })}
106
+ `
107
+ }
108
+
109
+ stateChanged(state: any) {
110
+ this.actions = state.route?.context?.actions?.filter((action: any) => !!action) || []
111
+ }
112
+ }
@@ -0,0 +1,99 @@
1
+ import '@material/mwc-icon'
2
+ import '@operato/input/ox-input-search.js'
3
+
4
+ import { css, html, LitElement } from 'lit'
5
+ import { customElement, property, query, state } from 'lit/decorators.js'
6
+ import { connect } from 'pwa-helpers'
7
+
8
+ import { openHelp } from '@operato/help'
9
+ import { store } from '@operato/shell'
10
+
11
+ @customElement('ox-title-bar')
12
+ export class TitleBar extends connect(store)(LitElement) {
13
+ static get styles() {
14
+ return css`
15
+ :host {
16
+ display: flex;
17
+ --help-icon-color: #fff;
18
+ --help-icon-hover-color: #fff;
19
+ --help-icon-opacity: 0.2;
20
+ --help-icon-size: 20px;
21
+
22
+ --mdc-icon-size: 20px;
23
+ }
24
+
25
+ [titler] {
26
+ display: flex;
27
+ font: var(--context-toolbar-title);
28
+ }
29
+
30
+ [titler] > * {
31
+ align-self: center;
32
+ }
33
+
34
+ [help] {
35
+ opacity: var(--help-icon-opacity);
36
+ font-size: var(--help-icon-size);
37
+ color: var(--help-icon-color);
38
+ }
39
+
40
+ [help]:hover {
41
+ cursor: help;
42
+ opacity: var(--help-icon-hover-opacity);
43
+ color: var(--help-icon-hover-color);
44
+ }
45
+
46
+ [search] {
47
+ display: flex;
48
+ justify-content: space-between;
49
+ align-items: center;
50
+
51
+ align-self: center;
52
+ color: var(--secondary-color);
53
+ background-color: var(--opacity-light-color);
54
+ border-radius: 999em;
55
+ padding: 0 10px;
56
+ }
57
+
58
+ [search] > * {
59
+ align-self: center;
60
+ }
61
+ `
62
+ }
63
+
64
+ @state() private context: any
65
+
66
+ render() {
67
+ const { search, filter, title, help } = this.context || ({} as any)
68
+ const { value = '', handler: searchHandler, placeholder = '' } = search || ({} as any)
69
+ const { handler: filterHandler } = filter || ({} as any)
70
+ const { icon, text = title } = typeof title === 'object' ? title : ({} as any)
71
+ const searchable = typeof searchHandler == 'function'
72
+ const filterable = typeof filterHandler == 'function'
73
+
74
+ return html`
75
+ ${title ? html` <div titler>${icon ? html`<mwc-icon>${icon}</mwc-icon>&nbsp;` : html``}${text}</div> ` : html``}
76
+ ${help ? html` &nbsp;<mwc-icon @click=${(e: MouseEvent) => openHelp(help)} help>help</mwc-icon> ` : html``}
77
+ ${searchable || filterable
78
+ ? html`
79
+ <div search>
80
+ ${searchable
81
+ ? html` <ox-input-search
82
+ .placeholder=${placeholder}
83
+ .value=${value || ''}
84
+ @change=${(e: Event) => {
85
+ searchHandler((e.target as any).value)
86
+ }}
87
+ ></ox-input-search>`
88
+ : html``}
89
+ ${filterable ? html`<mwc-icon @click=${(e: MouseEvent) => filterHandler()}>tune</mwc-icon>` : html``}
90
+ </div>
91
+ `
92
+ : html``}
93
+ `
94
+ }
95
+
96
+ stateChanged(state: any) {
97
+ this.context = state.route.context
98
+ }
99
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2018",
4
+ "module": "esnext",
5
+ "moduleResolution": "node",
6
+ "noEmitOnError": true,
7
+ "lib": ["es2017", "dom"],
8
+ "strict": true,
9
+ "esModuleInterop": false,
10
+ "allowSyntheticDefaultImports": true,
11
+ "experimentalDecorators": true,
12
+ "useDefineForClassFields": false,
13
+ "importHelpers": true,
14
+ "outDir": "dist",
15
+ "sourceMap": true,
16
+ "inlineSources": true,
17
+ "rootDir": "./",
18
+ "declaration": true,
19
+ "incremental": true,
20
+ "types": ["node", "mocha"]
21
+ },
22
+ "include": ["**/*.ts"]
23
+ }
@@ -0,0 +1,27 @@
1
+ // import { hmrPlugin, presets } from '@open-wc/dev-server-hmr';
2
+
3
+ /** Use Hot Module replacement by adding --hmr to the start command */
4
+ const hmr = process.argv.includes('--hmr');
5
+
6
+ export default /** @type {import('@web/dev-server').DevServerConfig} */ ({
7
+ open: '/demo/',
8
+ /** Use regular watch mode if HMR is not enabled. */
9
+ watch: !hmr,
10
+ /** Resolve bare module imports */
11
+ nodeResolve: {
12
+ exportConditions: ['browser', 'development'],
13
+ },
14
+
15
+ /** Compile JS for older browsers. Requires @web/dev-server-esbuild plugin */
16
+ // esbuildTarget: 'auto'
17
+
18
+ /** Set appIndex to enable SPA routing */
19
+ // appIndex: 'demo/index.html',
20
+
21
+ plugins: [
22
+ /** Use Hot Module Replacement by uncommenting. Requires @open-wc/dev-server-hmr plugin */
23
+ // hmr && hmrPlugin({ exclude: ['**/*/node_modules/**/*'], presets: [presets.litElement] }),
24
+ ],
25
+
26
+ // See documentation for all available options
27
+ });
@@ -0,0 +1,41 @@
1
+ // import { playwrightLauncher } from '@web/test-runner-playwright';
2
+
3
+ const filteredLogs = ['Running in dev mode', 'lit-html is in dev mode'];
4
+
5
+ export default /** @type {import("@web/test-runner").TestRunnerConfig} */ ({
6
+ /** Test files to run */
7
+ files: 'dist/test/**/*.test.js',
8
+
9
+ /** Resolve bare module imports */
10
+ nodeResolve: {
11
+ exportConditions: ['browser', 'development'],
12
+ },
13
+
14
+ /** Filter out lit dev mode logs */
15
+ filterBrowserLogs(log) {
16
+ for (const arg of log.args) {
17
+ if (typeof arg === 'string' && filteredLogs.some(l => arg.includes(l))) {
18
+ return false;
19
+ }
20
+ }
21
+ return true;
22
+ },
23
+
24
+ /** Compile JS for older browsers. Requires @web/dev-server-esbuild plugin */
25
+ // esbuildTarget: 'auto',
26
+
27
+ /** Amount of browsers to run concurrently */
28
+ // concurrentBrowsers: 2,
29
+
30
+ /** Amount of test files per browser to test concurrently */
31
+ // concurrency: 1,
32
+
33
+ /** Browsers to run tests on */
34
+ // browsers: [
35
+ // playwrightLauncher({ product: 'chromium' }),
36
+ // playwrightLauncher({ product: 'firefox' }),
37
+ // playwrightLauncher({ product: 'webkit' }),
38
+ // ],
39
+
40
+ // See documentation for all available options
41
+ });