@operato/data-grist 0.2.35 → 0.2.36

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 (98) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/custom-elements.json +1697 -5
  3. package/demo/data-grist-test.html +1 -1
  4. package/demo/index.html +18 -1
  5. package/dist/src/data-card/data-card-gutter-menu.js +2 -2
  6. package/dist/src/data-card/data-card-gutter-menu.js.map +1 -1
  7. package/dist/src/data-card/data-card-gutter.d.ts +13 -1
  8. package/dist/src/data-card/data-card-gutter.js +1 -0
  9. package/dist/src/data-card/data-card-gutter.js.map +1 -1
  10. package/dist/src/data-grid/data-grid-field.d.ts +2 -2
  11. package/dist/src/data-grid/data-grid-field.js.map +1 -1
  12. package/dist/src/data-grid/data-grid-header.d.ts +3 -0
  13. package/dist/src/data-grid/data-grid-header.js +38 -10
  14. package/dist/src/data-grid/data-grid-header.js.map +1 -1
  15. package/dist/src/data-grist.d.ts +9 -7
  16. package/dist/src/data-grist.js +26 -18
  17. package/dist/src/data-grist.js.map +1 -1
  18. package/dist/src/data-list/data-list-field.js +3 -3
  19. package/dist/src/data-list/data-list-field.js.map +1 -1
  20. package/dist/src/data-list/data-list-gutter.d.ts +13 -1
  21. package/dist/src/data-list/data-list-gutter.js +1 -0
  22. package/dist/src/data-list/data-list-gutter.js.map +1 -1
  23. package/dist/src/data-report/data-report-field.d.ts +2 -2
  24. package/dist/src/data-report/data-report-field.js.map +1 -1
  25. package/dist/src/filters/index.d.ts +2 -0
  26. package/dist/src/filters/index.js +3 -0
  27. package/dist/src/filters/index.js.map +1 -0
  28. package/dist/src/filters/list-select.d.ts +3 -0
  29. package/dist/src/filters/list-select.js +12 -0
  30. package/dist/src/filters/list-select.js.map +1 -0
  31. package/dist/src/filters/registry.d.ts +7 -0
  32. package/dist/src/filters/registry.js +40 -0
  33. package/dist/src/filters/registry.js.map +1 -0
  34. package/dist/src/index.d.ts +2 -0
  35. package/dist/src/index.js +2 -0
  36. package/dist/src/index.js.map +1 -1
  37. package/dist/src/interfaces/index.d.ts +2 -0
  38. package/dist/src/interfaces/index.js +3 -0
  39. package/dist/src/interfaces/index.js.map +1 -0
  40. package/dist/src/interfaces/ox-grist-search-form.d.ts +6 -0
  41. package/dist/src/interfaces/ox-grist-search-form.js +2 -0
  42. package/dist/src/interfaces/ox-grist-search-form.js.map +1 -0
  43. package/dist/src/interfaces/ox-search-field.d.ts +39 -0
  44. package/dist/src/interfaces/ox-search-field.js +2 -0
  45. package/dist/src/interfaces/ox-search-field.js.map +1 -0
  46. package/dist/src/search-form/index.d.ts +7 -0
  47. package/dist/src/search-form/index.js +8 -0
  48. package/dist/src/search-form/index.js.map +1 -0
  49. package/dist/src/search-form/ox-basic-field.d.ts +18 -0
  50. package/dist/src/search-form/ox-basic-field.js +75 -0
  51. package/dist/src/search-form/ox-basic-field.js.map +1 -0
  52. package/dist/src/search-form/ox-checkbox-field.d.ts +11 -0
  53. package/dist/src/search-form/ox-checkbox-field.js +60 -0
  54. package/dist/src/search-form/ox-checkbox-field.js.map +1 -0
  55. package/dist/src/search-form/ox-grist-search-form.d.ts +11 -0
  56. package/dist/src/search-form/ox-grist-search-form.js +177 -0
  57. package/dist/src/search-form/ox-grist-search-form.js.map +1 -0
  58. package/dist/src/search-form/ox-number-field.d.ts +14 -0
  59. package/dist/src/search-form/ox-number-field.js +112 -0
  60. package/dist/src/search-form/ox-number-field.js.map +1 -0
  61. package/dist/src/search-form/ox-search-form.d.ts +15 -0
  62. package/dist/src/search-form/ox-search-form.js +53 -0
  63. package/dist/src/search-form/ox-search-form.js.map +1 -0
  64. package/dist/src/search-form/ox-select-field.d.ts +21 -0
  65. package/dist/src/search-form/ox-select-field.js +181 -0
  66. package/dist/src/search-form/ox-select-field.js.map +1 -0
  67. package/dist/src/search-form/ox-text-field.d.ts +11 -0
  68. package/dist/src/search-form/ox-text-field.js +60 -0
  69. package/dist/src/search-form/ox-text-field.js.map +1 -0
  70. package/dist/src/types.d.ts +20 -3
  71. package/dist/src/types.js.map +1 -1
  72. package/dist/tsconfig.tsbuildinfo +1 -1
  73. package/package.json +7 -6
  74. package/src/data-card/data-card-gutter-menu.ts +2 -2
  75. package/src/data-card/data-card-gutter.ts +3 -3
  76. package/src/data-grid/data-grid-field.ts +2 -2
  77. package/src/data-grid/data-grid-header.ts +42 -11
  78. package/src/data-grist.ts +30 -20
  79. package/src/data-list/data-list-field.ts +3 -3
  80. package/src/data-list/data-list-gutter.ts +3 -3
  81. package/src/data-report/data-report-field.ts +2 -2
  82. package/src/filters/index.ts +3 -0
  83. package/src/filters/list-select.ts +14 -0
  84. package/src/filters/registry.ts +48 -0
  85. package/src/index.ts +3 -0
  86. package/src/interfaces/index.ts +2 -0
  87. package/src/interfaces/ox-grist-search-form.ts +7 -0
  88. package/src/interfaces/ox-search-field.ts +52 -0
  89. package/src/search-form/index.ts +7 -0
  90. package/src/search-form/ox-basic-field.ts +86 -0
  91. package/src/search-form/ox-checkbox-field.ts +57 -0
  92. package/src/search-form/ox-grist-search-form.ts +200 -0
  93. package/src/search-form/ox-number-field.ts +113 -0
  94. package/src/search-form/ox-search-form.ts +71 -0
  95. package/src/search-form/ox-select-field.ts +188 -0
  96. package/src/search-form/ox-text-field.ts +55 -0
  97. package/src/types.ts +28 -3
  98. package/yarn-error.log +2427 -3205
@@ -0,0 +1,113 @@
1
+ import '@material/mwc-icon'
2
+
3
+ import { css, CSSResult, html, TemplateResult } from 'lit'
4
+ import { ifDefined } from 'lit-html/directives/if-defined.js'
5
+ import { customElement, property } from 'lit/decorators.js'
6
+
7
+ import { OXNumberFieldProps } from '..'
8
+ import { OXBasicField } from './ox-basic-field'
9
+
10
+ @customElement('ox-number-field')
11
+ export class OXNumberField extends OXBasicField {
12
+ @property({ type: Object }) field!: OXNumberFieldProps
13
+ @property({ type: Number }) value?: number
14
+
15
+ setDefaultValue(defaultValue: number): void {
16
+ this.value = defaultValue
17
+ }
18
+
19
+ static styles: CSSResult[] = [
20
+ css`
21
+ :host {
22
+ display: inline-flex;
23
+ flex-direction: column;
24
+ }
25
+ input::-webkit-outer-spin-button,
26
+ input::-webkit-inner-spin-button {
27
+ -webkit-appearance: none;
28
+ margin: 0;
29
+ }
30
+
31
+ span.input-wrapper {
32
+ display: inline-flex;
33
+ }
34
+
35
+ button {
36
+ padding: var(--padding-narrow);
37
+ height: 24px;
38
+ background-color: var(--theme-white-color);
39
+ border: 1px solid rgba(var(--primary-color-rgb), 0.5);
40
+ border-radius: 50%;
41
+ opacity: 0.6;
42
+ cursor: pointer;
43
+ }
44
+ button mwc-icon {
45
+ font-size: var(--fontsize-default, 16px);
46
+ color: var(--primary-text-color, #476172);
47
+ }
48
+ button:hover {
49
+ opacity: 1;
50
+ }
51
+ input {
52
+ border: none;
53
+ outline: none;
54
+ padding: 4px 9px;
55
+ text-align: center;
56
+ font-size: var(--fontsize-default, 14px);
57
+ color: var(--primary-text-color, #476172);
58
+ }
59
+ `
60
+ ]
61
+
62
+ render(): TemplateResult {
63
+ const { name, hidden, id, placeholder, min, max, step } = this.field
64
+
65
+ return html`
66
+ <span class="input-wrapper" ?hidden=${hidden}>
67
+ <button @click=${this.decrease.bind(this)}>
68
+ <mwc-icon>add</mwc-icon>
69
+ </button>
70
+ <input
71
+ id=${ifDefined(id)}
72
+ name=${name}
73
+ type="number"
74
+ placeholder=${ifDefined(placeholder)}
75
+ min=${ifDefined(min)}
76
+ max=${ifDefined(max)}
77
+ step=${ifDefined(step)}
78
+ .value=${String(this.value) || ''}
79
+ @change=${this.onChangeHandler.bind(this)}
80
+ />
81
+ <button @click=${this.increase.bind(this)}>
82
+ <mwc-icon>remove</mwc-icon>
83
+ </button>
84
+ </span>
85
+ `
86
+ }
87
+
88
+ decrease(): void {
89
+ let fractionDigits: number = 0
90
+ if (this.field.step) {
91
+ fractionDigits = String(this.field.step).split('.')[1].length
92
+ }
93
+
94
+ this.value = Number(((this.value ?? 0) - (this.field.step || 1)).toFixed(fractionDigits))
95
+ }
96
+
97
+ increase(): void {
98
+ let fractionDigits: number = 0
99
+ if (this.field.step) {
100
+ fractionDigits = String(this.field.step).split('.')[1].length
101
+ }
102
+
103
+ this.value = Number(((this.value ?? 0) + (this.field.step || 1)).toFixed(fractionDigits))
104
+ }
105
+
106
+ private onChangeHandler(): void {
107
+ if (this.input.value) {
108
+ this.value = Number(this.input.value)
109
+ } else {
110
+ this.value = undefined
111
+ }
112
+ }
113
+ }
@@ -0,0 +1,71 @@
1
+ import './ox-text-field'
2
+ import './ox-checkbox-field'
3
+ import './ox-select-field'
4
+ import './ox-number-field'
5
+
6
+ import { LitElement, TemplateResult, html } from 'lit'
7
+ import {
8
+ OXCheckboxField,
9
+ OXCheckboxFieldProps,
10
+ OXNumberField,
11
+ OXNumberFieldProps,
12
+ OXSearchFieldProps,
13
+ OXSelectField,
14
+ OXSelectFieldProps,
15
+ OXTextField,
16
+ OXTextFieldProps
17
+ } from '..'
18
+ import { customElement, property, query, queryAll } from 'lit/decorators.js'
19
+
20
+ export type OXSearchFieldTypes = OXTextField | OXNumberField | OXSelectField | OXCheckboxField
21
+
22
+ @customElement('ox-search-form')
23
+ export class OXSearchForm extends LitElement {
24
+ @property({ type: Array }) fields!: OXSearchFieldProps[]
25
+
26
+ @query('form') form!: HTMLFormElement
27
+
28
+ render(): TemplateResult {
29
+ return html`
30
+ <form @submit-field=${this._submitFieldHandler.bind(this)}>
31
+ <slot></slot>
32
+ ${this.fields.map(this.renderField)}
33
+ </form>
34
+ `
35
+ }
36
+
37
+ get searchFields(): OXSearchFieldTypes[] {
38
+ return Array.from(this.form.querySelectorAll('*'))
39
+ }
40
+
41
+ private renderField(field: OXSearchFieldProps): TemplateResult {
42
+ const { type } = field
43
+
44
+ switch (type) {
45
+ case 'text':
46
+ return html`<ox-text-field .field=${field as OXTextFieldProps}></ox-text-field>`
47
+
48
+ case 'number':
49
+ return html`<ox-number-field .field=${field as OXNumberFieldProps}></ox-number-field>`
50
+
51
+ case 'select':
52
+ return html`<ox-select-field .field=${field as OXSelectFieldProps}></ox-select-field>`
53
+
54
+ case 'checkbox':
55
+ return html`<ox-checkbox-field .field=${field as OXCheckboxFieldProps}></ox-checkbox-field>`
56
+
57
+ default:
58
+ return html`<ox-text-field .field=${field as OXTextFieldProps}></ox-text-field>`
59
+ }
60
+ }
61
+
62
+ _submitFieldHandler(): void {
63
+ this.dispatchEvent(
64
+ new CustomEvent('submit', {
65
+ composed: true,
66
+ bubbles: true,
67
+ cancelable: true
68
+ })
69
+ ) && this.form.submit()
70
+ }
71
+ }
@@ -0,0 +1,188 @@
1
+ import '@operato/popup'
2
+ import '@material/mwc-icon'
3
+
4
+ import { CSSResult, TemplateResult, css, html } from 'lit'
5
+ import { OXFieldOptionProps, OXSelectFieldProps } from '..'
6
+ import { customElement, property } from 'lit/decorators.js'
7
+
8
+ import { OXBasicField } from './ox-basic-field'
9
+ import { OxPopupList } from '@operato/popup'
10
+ import { ifDefined } from 'lit-html/directives/if-defined.js'
11
+
12
+ @customElement('ox-select-field')
13
+ export class OXSelectField extends OXBasicField {
14
+ @property({ type: Object }) field!: OXSelectFieldProps
15
+ @property({ type: Boolean }) checked: boolean = false
16
+ @property({ type: String }) searchCondition: string = ''
17
+ @property({ type: String }) value: string = ''
18
+
19
+ static styles: CSSResult[] = [
20
+ css`
21
+ :host > label {
22
+ display: inline-flex;
23
+ flex-direction: column;
24
+ font-size: var(--fontsize-default, 14px);
25
+ color: var(--primary-text-color, #476172);
26
+ }
27
+ .input-wrapper {
28
+ display: inline-flex;
29
+ }
30
+ .input-wrapper mwc-icon {
31
+ opacity: 0.7;
32
+ font-size: var(--fontsize-default, 16px);
33
+ color: var(--primary-text-color, #476172);
34
+ line-height: 2;
35
+ }
36
+ input {
37
+ border: none;
38
+ outline: none;
39
+ padding: 4px 9px;
40
+ font-size: var(--fontsize-default, 14px);
41
+ color: var(--primary-text-color, #476172);
42
+ }
43
+ .search-input-wrapper {
44
+ display: inline-flex;
45
+ border-bottom: var(--border-dark-color, 1px solid rgba(0, 0, 0, 0.15));
46
+ }
47
+ .search-input-wrapper mwc-icon {
48
+ opacity: 0.7;
49
+ font-size: 20px;
50
+ color: var(--primary-text-color, #476172);
51
+ }
52
+ label[option] {
53
+ display: inline-flex;
54
+ margin: 5px 0px;
55
+ gap: 5px;
56
+ }
57
+ `
58
+ ]
59
+
60
+ get input(): HTMLInputElement {
61
+ const input: HTMLInputElement | null = this.renderRoot.querySelector('input[readonly]')
62
+ if (!input) throw new Error('Failed to find input element')
63
+
64
+ return input
65
+ }
66
+
67
+ get checkedOption(): OXFieldOptionProps {
68
+ if (!this.value) throw new Error('Value is not defined yet')
69
+
70
+ const checkedOption: OXFieldOptionProps | undefined = this.field.options.find(
71
+ (option: OXFieldOptionProps) => option.value === this.value
72
+ )
73
+ if (!checkedOption) throw new Error('No checked option found')
74
+
75
+ return checkedOption
76
+ }
77
+
78
+ get displayValue(): string {
79
+ try {
80
+ return this.checkedOption.name || this.checkedOption.value
81
+ } catch (e) {
82
+ return ''
83
+ }
84
+ }
85
+
86
+ get searchInput(): HTMLInputElement {
87
+ const searchInput: HTMLInputElement | null = this.renderRoot.querySelector('input#search-input')
88
+ if (!searchInput) throw new Error('No search input found')
89
+
90
+ return searchInput
91
+ }
92
+
93
+ setDefaultValue(defaultValue: any): void {
94
+ const input: HTMLInputElement | null = this.renderRoot.querySelector(`input[value="${defaultValue}"]`)
95
+ if (!input) return
96
+ input.checked = true
97
+
98
+ const option: OXFieldOptionProps | undefined = this.field.options.find(
99
+ (option: OXFieldOptionProps) => option.value === defaultValue
100
+ )
101
+ if (!option) throw new Error('No matched option found')
102
+
103
+ this.value = option.name || option.value
104
+ }
105
+
106
+ render(): TemplateResult {
107
+ const { name, hidden, id, options, searchEnable = true, placeholder } = this.field
108
+
109
+ return html`
110
+ <span class="input-wrapper" ?hidden=${hidden} @click=${this.openPopup.bind(this)}>
111
+ <input
112
+ id=${ifDefined(id)}
113
+ name=${name}
114
+ readonly
115
+ placeholder=${ifDefined(placeholder)}
116
+ .value=${this.displayValue}
117
+ />
118
+ <mwc-icon>keyboard_arrow_down</mwc-icon>
119
+ </span>
120
+
121
+ <ox-popup-list id="popup-list" multiple>
122
+ ${searchEnable
123
+ ? html`
124
+ <div class="search-input-wrapper">
125
+ <input id="search-input" type="text" @input=${this.onSearchInputHandler.bind(this)} />
126
+ <mwc-icon>search</mwc-icon>
127
+ </div>
128
+ `
129
+ : ''}
130
+ ${options
131
+ .filter(
132
+ (option: OXFieldOptionProps) =>
133
+ option.value.indexOf(this.searchCondition) >= 0 ||
134
+ (option.name && option?.name.indexOf(this.searchCondition) >= 0)
135
+ )
136
+ .map(
137
+ (option: OXFieldOptionProps) => html`
138
+ <label option>
139
+ <input
140
+ name=${name}
141
+ type="checkbox"
142
+ .value=${option.value}
143
+ @change=${this.onValueChange.bind(this)}
144
+ .checked=${this.value === option.value}
145
+ />
146
+ <span>${option.name || option.value}</span>
147
+ </label>
148
+ `
149
+ )}
150
+ </ox-popup-list>
151
+ `
152
+ }
153
+
154
+ openPopup(event: MouseEvent): void {
155
+ const popupList: OxPopupList | null = this.renderRoot.querySelector('#popup-list')
156
+ if (!popupList) throw new Error('Failed to find popup element')
157
+
158
+ const offsetHeight: number = this.input.offsetHeight
159
+ const { x, y } = this.input.getBoundingClientRect()
160
+
161
+ popupList.open({ left: x, top: y + offsetHeight })
162
+ }
163
+
164
+ onSearchInputHandler(): void {
165
+ this.searchCondition = this.searchInput.value
166
+ }
167
+
168
+ onValueChange(event: Event): void {
169
+ if (this.value) {
170
+ const prevCheckedInput: HTMLInputElement | null = this.renderRoot.querySelector(`input[value=${this.value}`)
171
+ if (prevCheckedInput) prevCheckedInput.checked = false
172
+ }
173
+
174
+ const checkedInput: HTMLInputElement | null = event.currentTarget as HTMLInputElement | null
175
+ if (!checkedInput) throw new Error('No checked input found')
176
+
177
+ if (checkedInput.value === this.value) {
178
+ this.value = ''
179
+ } else {
180
+ const foundOption: OXFieldOptionProps | undefined = this.field.options.find(
181
+ (option: OXFieldOptionProps) => option.value === checkedInput.value
182
+ )
183
+ if (!foundOption) throw new Error('No matched option found')
184
+
185
+ this.value = foundOption.value
186
+ }
187
+ }
188
+ }
@@ -0,0 +1,55 @@
1
+ import { css, html, TemplateResult } from 'lit'
2
+ import { customElement, property } from 'lit/decorators.js'
3
+ import { ifDefined } from 'lit/directives/if-defined.js'
4
+
5
+ import { OXTextFieldProps } from '..'
6
+ import { OXBasicField } from './ox-basic-field'
7
+
8
+ @customElement('ox-text-field')
9
+ export class OXTextField extends OXBasicField {
10
+ @property({ type: Object }) field!: OXTextFieldProps
11
+ @property({ type: String }) value: string = ''
12
+
13
+ static styles = [
14
+ ...OXBasicField.styles,
15
+ css`
16
+ label {
17
+ display: inline-flex;
18
+ flex-direction: column;
19
+ }
20
+ span.input-wrapper {
21
+ display: inline-flex;
22
+ border-bottom: var(--border-dark-color, 1px solid rgba(0, 0, 0, 0.15));
23
+ }
24
+ `
25
+ ]
26
+
27
+ setDefaultValue(defaultValue: string) {
28
+ this.value = defaultValue
29
+ }
30
+
31
+ render(): TemplateResult {
32
+ if (!this.field) return html``
33
+
34
+ const { name, hidden, id, placeholder } = this.field
35
+
36
+ return html`
37
+ <label>
38
+ <span class="input-wrapper" ?hidden=${hidden}>
39
+ <input
40
+ id=${ifDefined(id)}
41
+ name=${name}
42
+ type="text"
43
+ placeholder=${ifDefined(placeholder)}
44
+ .value=${this.value}
45
+ @input=${this.onChangeHandler.bind(this)}
46
+ />
47
+ </span>
48
+ </label>
49
+ `
50
+ }
51
+
52
+ private onChangeHandler(): void {
53
+ this.value = this.input.value
54
+ }
55
+ }
package/src/types.ts CHANGED
@@ -1,3 +1,13 @@
1
+ import { DataCardField } from './data-card/data-card-field'
2
+ import { DataCardGutter } from './data-card/data-card-gutter'
3
+ import { DataGridField } from './data-grid/data-grid-field'
4
+ import { DataListField } from './data-list/data-list-field'
5
+ import { DataListGutter } from './data-list/data-list-gutter'
6
+ import { DataReportField } from './data-report/data-report-field'
7
+ import { RecordCard } from './data-card/record-card'
8
+ import { RecordPartial } from './data-list/record-partial'
9
+ import { TemplateResult } from 'lit-html'
10
+
1
11
  export type GristConfig = {
2
12
  columns: ColumnConfig[]
3
13
  rows: RowsConfig
@@ -8,6 +18,7 @@ export type GristConfig = {
8
18
 
9
19
  export type SorterConfig = { name: string; desc?: boolean }
10
20
  export type SortersConfig = SorterConfig[]
21
+ export type FilterConfig = { type: string; options?: { [key: string]: any } }
11
22
 
12
23
  export type PaginationConfig = {
13
24
  page?: number
@@ -39,6 +50,7 @@ export type ColumnConfig = {
39
50
  width?: number | string | ColumnWidthCallback
40
51
  forList?: boolean
41
52
  validation?: ValidationCallback
53
+ filter?: FilterConfig
42
54
  imex?: ImexConfig
43
55
  multiple?: boolean
44
56
  }
@@ -71,9 +83,22 @@ export type RecordConfig = {
71
83
  rowOptionField?: string
72
84
  }
73
85
 
74
- export type FieldRenderer = (value: any, column: ColumnConfig, record: GristRecord, rowIndex: number, owner: any) => any
75
- export type FieldEditor = (value: any, column: ColumnConfig, record: GristRecord, rowIndex: number, owner: any) => any
76
- export type FieldThumbnailRenderer = (record: GristRecord, rowIndex: number) => any
86
+ export type FieldRenderer = (
87
+ value: any,
88
+ column: ColumnConfig,
89
+ record: GristRecord,
90
+ rowIndex: number,
91
+ owner: RecordCard | DataCardGutter | DataCardField | DataListGutter | DataListField | RecordPartial | DataReportField
92
+ ) => TemplateResult | string | void
93
+ export type FieldEditor = (
94
+ value: any,
95
+ column: ColumnConfig,
96
+ record: GristRecord,
97
+ rowIndex: number,
98
+ field: DataGridField
99
+ ) => Element
100
+ export type FieldThumbnailRenderer = (record: GristRecord, rowIndex: number) => TemplateResult | string | void
101
+ export type FilterSelectRenderer = (column: ColumnConfig, owner: Element) => TemplateResult | string | void
77
102
 
78
103
  export type GristEventHandlerSet = {
79
104
  click?: GristEventHandler