@operato/list-state 10.15.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/CHANGELOG.md +11 -0
- package/README.md +11 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +161 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +1035 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/ox-empty-note.d.ts +16 -0
- package/dist/src/ox-empty-note.js +65 -0
- package/dist/src/ox-empty-note.js.map +1 -0
- package/dist/src/ox-list-state.d.ts +44 -0
- package/dist/src/ox-list-state.js +194 -0
- package/dist/src/ox-list-state.js.map +1 -0
- package/dist/test/ox-list-state.test.d.ts +1 -0
- package/dist/test/ox-list-state.test.js +65 -0
- package/dist/test/ox-list-state.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +73 -0
- package/test/ox-list-state.test.ts +84 -0
- package/translations/en.json +16 -0
- package/translations/ja.json +16 -0
- package/translations/ko.json +16 -0
- package/translations/ms.json +16 -0
- package/translations/zh.json +16 -0
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@operato/list-state",
|
|
3
|
+
"version": "10.15.0",
|
|
4
|
+
"description": "Says what a list is doing: loading, could not load, empty, empty because narrowed, cut short, more to load.",
|
|
5
|
+
"author": "heartyoh",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/src/index.js",
|
|
8
|
+
"module": "dist/src/index.js",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"operato": true,
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public",
|
|
13
|
+
"@operato:registry": "https://registry.npmjs.org"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/hatiolab/operato.git",
|
|
18
|
+
"directory": "webcomponents/list-state"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./dist/src/index.js",
|
|
22
|
+
"./package.json": "./package.json",
|
|
23
|
+
"./ox-list-state.js": "./dist/src/ox-list-state.js",
|
|
24
|
+
"./ox-empty-note.js": "./dist/src/ox-empty-note.js"
|
|
25
|
+
},
|
|
26
|
+
"typesVersions": {
|
|
27
|
+
"*": {
|
|
28
|
+
"ox-list-state.js": [
|
|
29
|
+
"dist/src/ox-list-state.d.ts"
|
|
30
|
+
],
|
|
31
|
+
"ox-empty-note.js": [
|
|
32
|
+
"dist/src/ox-empty-note.d.ts"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"analyze": "cem analyze --litelement",
|
|
38
|
+
"build": "tsc && npm run analyze -- --exclude dist",
|
|
39
|
+
"prepublishOnly": "tsc && npm run analyze -- --exclude dist",
|
|
40
|
+
"lint": "eslint --ext .ts,.html . --ignore-path .gitignore && prettier \"**/*.ts\" --check --ignore-path .gitignore",
|
|
41
|
+
"format": "eslint --ext .ts,.html . --fix --ignore-path .gitignore && prettier \"**/*.ts\" --write --ignore-path .gitignore",
|
|
42
|
+
"test": "tsc && wtr --coverage",
|
|
43
|
+
"test:watch": "tsc && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"wtr --watch\""
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@material/web": "^2.0.0",
|
|
47
|
+
"@operato/i18n": "^10.11.0",
|
|
48
|
+
"@operato/utils": "^10.15.0",
|
|
49
|
+
"lit": "^3.1.2"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@custom-elements-manifest/analyzer": "^0.10.0",
|
|
53
|
+
"@hatiolab/prettier-config": "^1.0.0",
|
|
54
|
+
"@open-wc/eslint-config": "^12.0.3",
|
|
55
|
+
"@open-wc/testing": "^4.0.0",
|
|
56
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
57
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
58
|
+
"@web/dev-server": "^0.4.0",
|
|
59
|
+
"@web/test-runner": "^0.19.0",
|
|
60
|
+
"concurrently": "^9.0.0",
|
|
61
|
+
"eslint": "^9.0.0",
|
|
62
|
+
"eslint-config-prettier": "^9.1.0",
|
|
63
|
+
"husky": "^9.0.11",
|
|
64
|
+
"i18next": "^24.2.1",
|
|
65
|
+
"lint-staged": "^15.2.2",
|
|
66
|
+
"prettier": "^3.2.5",
|
|
67
|
+
"tslib": "^2.3.1",
|
|
68
|
+
"typescript": "^5.0.4"
|
|
69
|
+
},
|
|
70
|
+
"customElements": "custom-elements.json",
|
|
71
|
+
"prettier": "@hatiolab/prettier-config",
|
|
72
|
+
"gitHead": "62ac2a23866e7ea579786c4433ef356888dfded8"
|
|
73
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* Elements are compared by presence, not by value: a failing element comparison makes chai render the DOM and hang the runner. */
|
|
2
|
+
import { expect, fixture, html, oneEvent } from '@open-wc/testing'
|
|
3
|
+
|
|
4
|
+
import '../src/ox-list-state.js'
|
|
5
|
+
import type { OxListState } from '../src/ox-list-state.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Every phase says something different, and the two that people confuse - empty and failed - never
|
|
9
|
+
* look alike. Checked by what is rendered, not by the phase value.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
async function state(props: Partial<OxListState>) {
|
|
13
|
+
const el = await fixture<OxListState>(html`<ox-list-state></ox-list-state>`)
|
|
14
|
+
Object.assign(el, props)
|
|
15
|
+
await el.updateComplete
|
|
16
|
+
return el
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const root = (el: OxListState) => el.renderRoot as ShadowRoot
|
|
20
|
+
const note = (el: OxListState) => root(el).querySelector('ox-empty-note') as HTMLElement | null
|
|
21
|
+
|
|
22
|
+
describe('ox-list-state', () => {
|
|
23
|
+
it('shows a spinner while nothing is held', async () => {
|
|
24
|
+
const el = await state({ loading: true, scale: { shown: 0, loaded: 0 } })
|
|
25
|
+
expect(root(el).querySelector('[role="status"]')).to.exist
|
|
26
|
+
expect(note(el) === null, 'absent').to.equal(true)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('says a failed load is a failure, not an empty list, and offers retry', async () => {
|
|
30
|
+
const el = await state({ error: new Error('down'), scale: { shown: 0, loaded: 0 } })
|
|
31
|
+
|
|
32
|
+
expect(note(el)?.getAttribute('role')).to.equal('alert')
|
|
33
|
+
expect(note(el)?.getAttribute('title')).to.equal('could not load the list')
|
|
34
|
+
|
|
35
|
+
const retry = note(el)!.querySelector('md-text-button') as HTMLElement
|
|
36
|
+
setTimeout(() => retry.click())
|
|
37
|
+
await oneEvent(el, 'retry')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('keeps rows that were loaded before, and says they are not a fresh result', async () => {
|
|
41
|
+
const el = await state({ error: new Error('down'), scale: { shown: 5, loaded: 5 } })
|
|
42
|
+
|
|
43
|
+
expect(note(el) === null, 'absent').to.equal(true)
|
|
44
|
+
const band = root(el).querySelector('.band[failed]')
|
|
45
|
+
expect(band?.getAttribute('role')).to.equal('alert')
|
|
46
|
+
expect(band?.textContent).to.contain('could not refresh')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('tells empty-because-narrowed apart from empty, with a way to clear', async () => {
|
|
50
|
+
const narrowed = await state({ scale: { shown: 0, loaded: 9, filtered: true } })
|
|
51
|
+
expect(note(narrowed)?.getAttribute('title')).to.equal('nothing matches')
|
|
52
|
+
|
|
53
|
+
const clear = note(narrowed)!.querySelector('md-text-button') as HTMLElement
|
|
54
|
+
setTimeout(() => clear.click())
|
|
55
|
+
await oneEvent(narrowed, 'reset-filters')
|
|
56
|
+
|
|
57
|
+
const empty = await state({ scale: { shown: 0, loaded: 0 }, emptyNote: { title: 'no work orders yet' } })
|
|
58
|
+
expect(note(empty)?.getAttribute('title')).to.equal('no work orders yet')
|
|
59
|
+
expect(note(empty)?.querySelector('md-text-button') === null, 'absent').to.equal(true)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('shows the reason for an empty list only when one is given', async () => {
|
|
63
|
+
const withReason = await state({ scale: { shown: 0, loaded: 0 }, reason: 'the source is not connected' })
|
|
64
|
+
expect(note(withReason)?.querySelector('.why')?.textContent).to.equal('the source is not connected')
|
|
65
|
+
|
|
66
|
+
const without = await state({ scale: { shown: 0, loaded: 0 } })
|
|
67
|
+
expect(note(without)?.querySelector('.why') === null, 'absent').to.equal(true)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('says a capped list was cut, and offers more when there is more', async () => {
|
|
71
|
+
const el = await state({ scale: { shown: 100, loaded: 100, capped: true } })
|
|
72
|
+
|
|
73
|
+
expect(root(el).querySelector('.band:not([failed])')?.textContent).to.contain('showing a partial result')
|
|
74
|
+
|
|
75
|
+
const more = root(el).querySelector('.more md-text-button') as HTMLElement
|
|
76
|
+
setTimeout(() => more.click())
|
|
77
|
+
await oneEvent(el, 'load-more')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('says nothing extra when everything is here', async () => {
|
|
81
|
+
const el = await state({ scale: { shown: 7, loaded: 7, total: 7 } })
|
|
82
|
+
expect(root(el).querySelector('.band, .more, ox-empty-note, [role="status"]') === null, 'absent').to.equal(true)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"text.list-capped-detail": "narrow by search or filter to see the rest.",
|
|
3
|
+
"text.list-capped-title": "showing a partial result.",
|
|
4
|
+
"text.list-clear-filters": "clear filters",
|
|
5
|
+
"text.list-count": "{shown}",
|
|
6
|
+
"text.list-count-capped": "first {loaded} shown",
|
|
7
|
+
"text.list-count-of-loaded": "{shown} of {loaded}",
|
|
8
|
+
"text.list-count-of-total": "{shown} of {total}",
|
|
9
|
+
"text.list-empty": "nothing here yet.",
|
|
10
|
+
"text.list-load-failed": "could not load the list",
|
|
11
|
+
"text.list-load-more": "load more",
|
|
12
|
+
"text.list-loading": "loading…",
|
|
13
|
+
"text.list-nothing-found": "nothing matches",
|
|
14
|
+
"text.list-refresh-failed": "could not refresh the list",
|
|
15
|
+
"text.list-retry": "retry"
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"text.list-capped-detail": "検索やフィルターで絞り込んでください。",
|
|
3
|
+
"text.list-capped-title": "一部のみ表示しています。",
|
|
4
|
+
"text.list-clear-filters": "条件を消去",
|
|
5
|
+
"text.list-count": "{shown}件",
|
|
6
|
+
"text.list-count-capped": "先頭{loaded}件のみ表示",
|
|
7
|
+
"text.list-count-of-loaded": "{loaded}件中 {shown}件",
|
|
8
|
+
"text.list-count-of-total": "全{total}件中 {shown}件",
|
|
9
|
+
"text.list-empty": "まだ何もありません。",
|
|
10
|
+
"text.list-load-failed": "一覧を読み込めませんでした",
|
|
11
|
+
"text.list-load-more": "さらに表示",
|
|
12
|
+
"text.list-loading": "読み込み中…",
|
|
13
|
+
"text.list-nothing-found": "該当なし",
|
|
14
|
+
"text.list-refresh-failed": "一覧を更新できませんでした",
|
|
15
|
+
"text.list-retry": "再試行"
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"text.list-capped-detail": "검색이나 필터로 좁혀 보세요.",
|
|
3
|
+
"text.list-capped-title": "일부만 표시하고 있습니다.",
|
|
4
|
+
"text.list-clear-filters": "조건 지우기",
|
|
5
|
+
"text.list-count": "{shown}건",
|
|
6
|
+
"text.list-count-capped": "처음 {loaded}건만 표시",
|
|
7
|
+
"text.list-count-of-loaded": "{loaded}건 중 {shown}건",
|
|
8
|
+
"text.list-count-of-total": "전체 {total}건 중 {shown}건",
|
|
9
|
+
"text.list-empty": "아직 아무것도 없습니다.",
|
|
10
|
+
"text.list-load-failed": "목록을 불러오지 못했습니다",
|
|
11
|
+
"text.list-load-more": "더 보기",
|
|
12
|
+
"text.list-loading": "불러오는 중…",
|
|
13
|
+
"text.list-nothing-found": "조건에 맞는 것이 없습니다",
|
|
14
|
+
"text.list-refresh-failed": "목록을 새로 불러오지 못했습니다",
|
|
15
|
+
"text.list-retry": "다시 시도"
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"text.list-capped-detail": "sempitkan dengan carian atau penapis.",
|
|
3
|
+
"text.list-capped-title": "memaparkan sebahagian hasil.",
|
|
4
|
+
"text.list-clear-filters": "kosongkan penapis",
|
|
5
|
+
"text.list-count": "{shown}",
|
|
6
|
+
"text.list-count-capped": "{loaded} yang pertama sahaja dipaparkan",
|
|
7
|
+
"text.list-count-of-loaded": "{shown} daripada {loaded}",
|
|
8
|
+
"text.list-count-of-total": "{shown} daripada {total}",
|
|
9
|
+
"text.list-empty": "belum ada apa-apa di sini.",
|
|
10
|
+
"text.list-load-failed": "senarai tidak dapat dimuatkan",
|
|
11
|
+
"text.list-load-more": "muat lagi",
|
|
12
|
+
"text.list-loading": "memuatkan…",
|
|
13
|
+
"text.list-nothing-found": "tiada yang sepadan",
|
|
14
|
+
"text.list-refresh-failed": "senarai tidak dapat dimuat semula",
|
|
15
|
+
"text.list-retry": "cuba lagi"
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"text.list-capped-detail": "请用搜索或筛选缩小范围。",
|
|
3
|
+
"text.list-capped-title": "仅显示部分结果。",
|
|
4
|
+
"text.list-clear-filters": "清除条件",
|
|
5
|
+
"text.list-count": "{shown} 条",
|
|
6
|
+
"text.list-count-capped": "仅显示前 {loaded} 条",
|
|
7
|
+
"text.list-count-of-loaded": "{loaded} 条中的 {shown} 条",
|
|
8
|
+
"text.list-count-of-total": "共 {total} 条中的 {shown} 条",
|
|
9
|
+
"text.list-empty": "这里还没有内容。",
|
|
10
|
+
"text.list-load-failed": "无法加载列表",
|
|
11
|
+
"text.list-load-more": "加载更多",
|
|
12
|
+
"text.list-loading": "加载中…",
|
|
13
|
+
"text.list-nothing-found": "没有匹配项",
|
|
14
|
+
"text.list-refresh-failed": "无法刷新列表",
|
|
15
|
+
"text.list-retry": "重试"
|
|
16
|
+
}
|