@operato/popup 10.15.0 → 10.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@operato/popup",
3
3
  "description": "Webcomponent popup following open-wc recommendations",
4
4
  "author": "heartyoh",
5
- "version": "10.15.0",
5
+ "version": "10.16.0",
6
6
  "type": "module",
7
7
  "main": "dist/src/index.js",
8
8
  "module": "dist/src/index.js",
@@ -72,8 +72,8 @@
72
72
  "dependencies": {
73
73
  "@material/web": "^2.0.0",
74
74
  "@operato/i18n": "^10.11.0",
75
- "@operato/styles": "^10.15.0",
76
- "@operato/utils": "^10.15.0",
75
+ "@operato/styles": "^10.16.0",
76
+ "@operato/utils": "^10.16.0",
77
77
  "lit": "^3.1.2",
78
78
  "sortablejs": "^1.15.2"
79
79
  },
@@ -110,5 +110,5 @@
110
110
  "prettier --write"
111
111
  ]
112
112
  },
113
- "gitHead": "62ac2a23866e7ea579786c4433ef356888dfded8"
113
+ "gitHead": "8a061bd85d68e619b0c6aef699c57c89d3d9052a"
114
114
  }
@@ -1,58 +1,125 @@
1
- import { expect } from '@open-wc/testing'
1
+ import '@material/web/button/filled-button.js'
2
2
 
3
- import { ACTION_HEIGHT_TOKENS, ButtonContainerStyles, CommonHeaderStyles, CommonPopupStyles } from '@operato/styles'
3
+ import { expect, fixture } from '@open-wc/testing'
4
+ import { css, html, LitElement } from 'lit'
4
5
 
6
+ import {
7
+ ACTION_HEIGHT_TOKENS,
8
+ ButtonContainerStyles,
9
+ CommonHeaderStyles,
10
+ CommonPopupStyles
11
+ } from '@operato/styles'
12
+
13
+ import '../src/ox-popup-actions.js'
5
14
  import { OxPopupActions } from '../src/ox-popup-actions.js'
6
15
 
7
16
  /**
8
- * Office button heights come from one token per use (ADR-0060 decision 6): 32px for action buttons,
9
- * 26px in a grid cell, the same for mouse and finger (WCAG 2.2 AA, user decision 2026-09-17).
17
+ * Button heights (ADR-0060 decisions 6 and 6-1).
18
+ *
19
+ * The **platform** keeps the heights every application had before the operato standardization, so
20
+ * dssp, fmsim and the rest do not change when they upgrade. The **operato products** are compact: the
21
+ * shell theme opens each token as `var(--suite-<token>, <platform>)` and operato-suite fills the slot.
10
22
  *
11
- * The finger branch cannot be emulated in the test browser, so these read the stylesheets: every
12
- * height is the token with the shared default, and no finger branch brings back a height of its own.
13
- * Shop-floor popups raise the token themselves (operato-plant checks that on its side).
23
+ * These render both worlds with a mouse (the test browser). The finger branches cannot be emulated,
24
+ * so those are read from the stylesheets.
14
25
  */
15
26
 
27
+ const PLATFORM_TOKENS = `
28
+ --context-action-height: var(--suite-context-action-height, 32px);
29
+ --popup-action-height: var(--suite-popup-action-height, 30px);
30
+ --form-action-height: var(--suite-form-action-height, 40px);
31
+ `
32
+ const SUITE_SLOTS = `
33
+ --suite-context-action-height: 32px;
34
+ --suite-popup-action-height: 32px;
35
+ --suite-form-action-height: 32px;
36
+ `
37
+
38
+ class StyledRows extends LitElement {
39
+ static styles = [CommonPopupStyles, CommonHeaderStyles, ButtonContainerStyles, css``]
40
+
41
+ render() {
42
+ return html`
43
+ <div class="buttons"><md-filled-button id="popup-md">save</md-filled-button></div>
44
+ <div class="footer"><button id="footer">save</button></div>
45
+ <div class="button-container">
46
+ <button id="bar">export</button>
47
+ <md-filled-button id="bar-md">publish</md-filled-button>
48
+ </div>
49
+ `
50
+ }
51
+ }
52
+ customElements.define('test-styled-rows', StyledRows)
53
+
54
+ const height = (el: Element | null | undefined) => Math.round(el!.getBoundingClientRect().height)
16
55
  const text = (styles: unknown) =>
17
56
  (Array.isArray(styles) ? styles : [styles]).map(s => String((s as { cssText?: string }).cssText ?? s)).join('\n')
57
+ const coarse = (cssText: string) => cssText.slice(cssText.indexOf('(pointer: coarse)'))
18
58
 
19
- const coarse = (css: string) => css.slice(css.indexOf('(pointer: coarse)'))
59
+ async function rows(style: string) {
60
+ const host = await fixture<HTMLElement>(html`
61
+ <div style="${style}">
62
+ <test-styled-rows></test-styled-rows>
63
+ <ox-popup-actions>
64
+ <md-filled-button slot="confirm" id="prompt-md">ok</md-filled-button>
65
+ </ox-popup-actions>
66
+ </div>
67
+ `)
68
+ const styled = host.querySelector('test-styled-rows') as StyledRows
69
+ await styled.updateComplete
70
+ const actions = host.querySelector('ox-popup-actions') as OxPopupActions
71
+ await actions.updateComplete
72
+ const buttons = [...Array.from(styled.shadowRoot!.querySelectorAll('md-filled-button')), host.querySelector('#prompt-md')]
73
+ await Promise.all(buttons.map(b => (b as LitElement).updateComplete))
74
+ return { root: styled.shadowRoot!, host }
75
+ }
20
76
 
21
77
  describe('office control height', () => {
22
- it('the defaults are 32 for action buttons and 26 in a grid cell', () => {
78
+ it('the platform defaults are the heights before the standardization', () => {
23
79
  expect(ACTION_HEIGHT_TOKENS).to.deep.equal({
24
80
  '--context-action-height': 32,
25
- '--popup-action-height': 32,
26
- '--form-action-height': 32,
81
+ '--popup-action-height': 30,
82
+ '--form-action-height': 40,
27
83
  '--cell-action-height': 26
28
84
  })
29
85
  })
30
86
 
31
- it('ox-popup-actions: the popup token, with no finger height of its own', () => {
32
- const css = text(OxPopupActions.styles)
33
- expect(css).to.contain('--_height: var(--popup-action-height, 32px)')
34
- expect(coarse(css)).to.not.contain('--_height')
35
- expect(css).to.not.match(/max\(\d+px/)
87
+ it('★ platform: nothing changes for applications outside the operato suite', async () => {
88
+ const { root, host } = await rows(PLATFORM_TOKENS)
89
+
90
+ expect(height(root.querySelector('#popup-md'))).to.equal(40) // CommonPopupStyles .buttons
91
+ expect(height(root.querySelector('#footer'))).to.be.at.least(30) // older footer
92
+ expect(height(root.querySelector('#bar'))).to.equal(32) // page action bar
93
+ expect(height(root.querySelector('#bar-md'))).to.equal(40) // Material keeps its own in the bar
94
+ expect(height(host.querySelector('#prompt-md'))).to.equal(30) // OxPrompt row, as before
36
95
  })
37
96
 
38
- it('CommonPopupStyles .buttons: the popup token for mouse and finger', () => {
39
- const css = text(CommonPopupStyles)
40
- expect(css).to.contain('--md-filled-button-container-height: var(--popup-action-height, 32px)')
41
- expect(coarse(css)).to.contain('min-height: var(--popup-action-height, 32px)')
42
- expect(css).to.not.match(/max\(\d+px/)
97
+ it('★ suite: every action button in the operato products is 32px', async () => {
98
+ const { root, host } = await rows(SUITE_SLOTS + PLATFORM_TOKENS)
99
+
100
+ expect(height(root.querySelector('#popup-md'))).to.equal(32)
101
+ expect(height(root.querySelector('#bar'))).to.equal(32)
102
+ expect(height(root.querySelector('#bar-md'))).to.equal(32)
103
+ expect(height(host.querySelector('#prompt-md'))).to.equal(32)
43
104
  })
44
105
 
45
- it('CommonHeaderStyles .footer: the old popup footer reads the same popup token', () => {
46
- const css = text(CommonHeaderStyles)
47
- expect(css).to.contain('min-height: var(--popup-action-height, 32px)')
48
- expect(css).to.not.contain('min-height: 30px')
106
+ it('a shop-floor screen still raises the popup row above both', async () => {
107
+ const { root } = await rows(SUITE_SLOTS + PLATFORM_TOKENS + '--popup-action-height: 64px;')
108
+ expect(height(root.querySelector('#popup-md'))).to.equal(64)
109
+ })
110
+
111
+ it('finger: the platform keeps 44px for the page bar and a 48px floor for the popup row', () => {
112
+ const bar = coarse(text(ButtonContainerStyles))
113
+ expect(bar).to.contain('var(--suite-context-action-height, 44px)')
114
+
115
+ const popup = coarse(text(CommonPopupStyles))
116
+ expect(popup).to.contain('var(--suite-popup-action-height, 48px)')
117
+ expect(popup).to.contain('var(--popup-action-height, 30px)')
49
118
  })
50
119
 
51
- it('ButtonContainerStyles: the page action token, and no finger branch', () => {
52
- const css = text(ButtonContainerStyles)
53
- expect(css).to.contain('height: var(--context-action-height, 32px)')
54
- expect(css).to.contain('--md-filled-button-container-height: var(--context-action-height, 32px)')
55
- expect(css).to.not.contain('--context-action-height-touch')
56
- expect(css).to.not.contain('(pointer: coarse)')
120
+ it('ox-popup-actions has no finger height of its own', () => {
121
+ const cssText = text(OxPopupActions.styles)
122
+ expect(cssText).to.contain('--_height: var(--popup-action-height, 30px)')
123
+ expect(coarse(cssText)).to.not.contain('--_height:')
57
124
  })
58
125
  })
@@ -11,7 +11,7 @@ import '../src/ox-popup-actions.js'
11
11
  * wrong by writing its markup differently. These run with a mouse pointer (the test browser); the
12
12
  * finger bar is a media query of the same element.
13
13
  *
14
- * The expected numbers are written out (32, 68, 999em) rather than read from the stylesheet, so a
14
+ * The expected numbers are written out (30, 68, 999em) rather than read from the stylesheet, so a
15
15
  * change to the stylesheet shows up here as a decision, not as a silent follow.
16
16
  */
17
17
 
@@ -60,7 +60,7 @@ describe('ox-popup-actions', () => {
60
60
  expect(Math.round(confirm.getBoundingClientRect().right)).to.equal(Math.round(actions.getBoundingClientRect().right))
61
61
  })
62
62
 
63
- it('gives Material buttons and plain buttons the same 32px height', async () => {
63
+ it('gives Material buttons and plain buttons the same height (30px, the platform default)', async () => {
64
64
  const actions = await row(html`
65
65
  <ox-popup-actions>
66
66
  <button slot="cancel" id="plain">cancel</button>
@@ -68,8 +68,8 @@ describe('ox-popup-actions', () => {
68
68
  </ox-popup-actions>
69
69
  `)
70
70
 
71
- expect(height(actions.querySelector('#md')!)).to.equal(32)
72
- expect(height(actions.querySelector('#plain')!)).to.equal(32)
71
+ expect(height(actions.querySelector('#md')!)).to.equal(30)
72
+ expect(height(actions.querySelector('#plain')!)).to.equal(30)
73
73
  })
74
74
 
75
75
  it('uses pill shapes inside the popup card', async () => {
@@ -62,8 +62,8 @@ describe('OxPrompt uses the shared action row', () => {
62
62
  expect(confirm.getAttribute('slot')).to.equal('confirm')
63
63
  expect(cancel.getAttribute('slot')).to.equal('cancel')
64
64
  expect(cancel.getBoundingClientRect().left).to.be.lessThan(confirm.getBoundingClientRect().left)
65
- expect(Math.round(confirm.getBoundingClientRect().height)).to.equal(32)
66
- expect(Math.round(cancel.getBoundingClientRect().height)).to.equal(32)
65
+ expect(Math.round(confirm.getBoundingClientRect().height)).to.equal(30)
66
+ expect(Math.round(cancel.getBoundingClientRect().height)).to.equal(30)
67
67
 
68
68
  cancel.click()
69
69
  expect(await within(answer)).to.equal(false)
@@ -97,3 +97,40 @@ describe('OxPrompt uses the shared action row', () => {
97
97
  expect(await within(answer)).to.equal(false)
98
98
  })
99
99
  })
100
+
101
+ describe('OxPrompt.confirmDelete', () => {
102
+ afterEach(() => {
103
+ document.body.querySelectorAll('ox-prompt').forEach(p => (p as OxPrompt).close())
104
+ })
105
+
106
+ async function opened(count?: number) {
107
+ const before = document.body.querySelectorAll('ox-prompt').length
108
+ const answer = OxPrompt.confirmDelete({ count })
109
+ for (let i = 0; i < 40 && document.body.querySelectorAll('ox-prompt').length <= before; i++) await settle()
110
+ const prompts = document.body.querySelectorAll('ox-prompt')
111
+ const prompt = prompts[prompts.length - 1] as OxPrompt
112
+ await prompt.updateComplete
113
+ const root = prompt.renderRoot as ShadowRoot
114
+ const confirm = root.querySelector('#confirm') as HTMLElement & { updateComplete: Promise<unknown> }
115
+ await confirm?.updateComplete
116
+ return { answer, prompt, root, confirm }
117
+ }
118
+
119
+ it('asks in the shared popup with a destructive delete, and answers true on delete', async () => {
120
+ const { answer, prompt, confirm } = await opened()
121
+
122
+ expect(prompt.titler).to.equal('delete the selected item?')
123
+ expect(confirm.hasAttribute('destructive')).to.equal(true)
124
+ expect(confirm.textContent?.trim()).to.equal('delete')
125
+
126
+ confirm.click()
127
+ expect(await within(answer)).to.equal(true)
128
+ })
129
+
130
+ it('names how many are selected when more than one', async () => {
131
+ const { prompt, root } = await opened(3)
132
+
133
+ expect(prompt.titler).to.equal('delete the 3 selected items?')
134
+ ;(root.querySelector('#cancel') as HTMLElement).click()
135
+ })
136
+ })
@@ -1 +1,6 @@
1
- {}
1
+ {
2
+ "button.popup-cancel": "cancel",
3
+ "button.popup-delete": "delete",
4
+ "text.popup-confirm-delete": "delete the selected item?",
5
+ "text.popup-confirm-delete-count": "delete the {count} selected items?"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "button.popup-cancel": "キャンセル",
3
+ "button.popup-delete": "削除",
4
+ "text.popup-confirm-delete": "選択した項目を削除しますか?",
5
+ "text.popup-confirm-delete-count": "選択した{count}件を削除しますか?"
6
+ }
@@ -1 +1,6 @@
1
- {}
1
+ {
2
+ "button.popup-cancel": "취소",
3
+ "button.popup-delete": "삭제",
4
+ "text.popup-confirm-delete": "선택한 항목을 삭제할까요?",
5
+ "text.popup-confirm-delete-count": "선택한 {count}건을 삭제할까요?"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "button.popup-cancel": "batal",
3
+ "button.popup-delete": "padam",
4
+ "text.popup-confirm-delete": "padam item yang dipilih?",
5
+ "text.popup-confirm-delete-count": "padam {count} item yang dipilih?"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "button.popup-cancel": "取消",
3
+ "button.popup-delete": "删除",
4
+ "text.popup-confirm-delete": "删除所选项目吗?",
5
+ "text.popup-confirm-delete-count": "删除所选的{count}项吗?"
6
+ }