@operato/input 8.0.0-alpha.1 → 8.0.0-alpha.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 (33) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/src/ox-input-file.js +3 -2
  3. package/dist/src/ox-input-file.js.map +1 -1
  4. package/dist/src/ox-input-signature.d.ts +6 -3
  5. package/dist/src/ox-input-signature.js +73 -48
  6. package/dist/src/ox-input-signature.js.map +1 -1
  7. package/dist/src/ox-select-floor.d.ts +38 -0
  8. package/dist/src/ox-select-floor.js +246 -0
  9. package/dist/src/ox-select-floor.js.map +1 -0
  10. package/dist/stories/image-for-select-floor.d.ts +1 -0
  11. package/dist/stories/image-for-select-floor.js +2 -0
  12. package/dist/stories/image-for-select-floor.js.map +1 -0
  13. package/dist/stories/{ox-input-/bsignature.stories.d.ts → ox-input-signature.stories.d.ts} +4 -2
  14. package/dist/stories/ox-input-signature.stories.js +60 -0
  15. package/dist/stories/ox-input-signature.stories.js.map +1 -0
  16. package/dist/stories/ox-select-floor.stories.d.ts +41 -0
  17. package/dist/stories/ox-select-floor.stories.js +140 -0
  18. package/dist/stories/ox-select-floor.stories.js.map +1 -0
  19. package/dist/tsconfig.tsbuildinfo +1 -1
  20. package/package.json +8 -4
  21. package/src/ox-input-file.ts +3 -2
  22. package/src/ox-input-signature.ts +72 -46
  23. package/src/ox-select-floor.ts +257 -0
  24. package/stories/image-for-select-floor.ts +2 -0
  25. package/stories/ox-input-signature.stories.ts +75 -0
  26. package/stories/ox-select-floor.stories.ts +169 -0
  27. package/assets/images/icon-editor-gradient-direction.png +0 -0
  28. package/assets/images/icon-properties-label.png +0 -0
  29. package/assets/images/icon-properties-line-type.png +0 -0
  30. package/assets/images/icon-properties-table.png +0 -0
  31. package/dist/stories/ox-input-/bsignature.stories.js +0 -56
  32. package/dist/stories/ox-input-/bsignature.stories.js.map +0 -1
  33. package/stories/ox-input-/bsignature.stories.ts +0 -71
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@operato/input",
3
3
  "description": "Webcomponents for input following open-wc recommendations",
4
4
  "author": "heartyoh@hatiolab.com",
5
- "version": "8.0.0-alpha.1",
5
+ "version": "8.0.0-alpha.19",
6
6
  "main": "dist/src/index.js",
7
7
  "module": "dist/src/index.js",
8
8
  "license": "MIT",
@@ -58,6 +58,7 @@
58
58
  "./ox-input-textarea.js": "./dist/src/ox-input-textarea.js",
59
59
  "./ox-input-direction.js": "./dist/src/ox-input-direction.js",
60
60
  "./ox-input-signature.js": "./dist/src/ox-input-signature.js",
61
+ "./ox-select-floor.js": "./dist/src/ox-select-floor.js",
61
62
  "./ox-input-table-column-config.js": "./dist/src/ox-input-table-column-config.js"
62
63
  },
63
64
  "typesVersions": {
@@ -179,6 +180,9 @@
179
180
  "./ox-input-signature.js": [
180
181
  "./dist/src/ox-input-signature.d.ts"
181
182
  ],
183
+ "./ox-select-floor.js": [
184
+ "./dist/src/ox-select-floor.d.ts"
185
+ ],
182
186
  "./ox-input-table-column-config.js": [
183
187
  "./dist/src/ox-input-table-column-config.d.ts"
184
188
  ]
@@ -212,8 +216,8 @@
212
216
  "@material/web": "^2.0.0",
213
217
  "@operato/color-picker": "^8.0.0-alpha.0",
214
218
  "@operato/i18n": "^8.0.0-alpha.0",
215
- "@operato/popup": "^8.0.0-alpha.0",
216
- "@operato/styles": "^8.0.0-alpha.0",
219
+ "@operato/popup": "^8.0.0-alpha.4",
220
+ "@operato/styles": "^8.0.0-alpha.4",
217
221
  "@operato/utils": "^8.0.0-alpha.0",
218
222
  "@polymer/paper-dropdown-menu": "^3.2.0",
219
223
  "@polymer/paper-item": "^3.0.1",
@@ -258,5 +262,5 @@
258
262
  "prettier --write"
259
263
  ]
260
264
  },
261
- "gitHead": "7e2725314b1cb79c18f27b5a8db9dccd2461a1a3"
265
+ "gitHead": "9656b17ec8476c2eeace042a4e561161539e81e7"
262
266
  }
@@ -79,12 +79,13 @@ export class OxInputFile extends OxFormField {
79
79
  border-bottom: var(--file-uploader-li-border-bottom);
80
80
  font-size: var(--md-sys-typescale-label-large-size, 0.875rem);
81
81
  color: var(--md-sys-color-on-primary-container);
82
+ display: flex;
83
+ align-items: center;
82
84
  }
83
85
  li md-icon {
84
- float: right;
85
86
  cursor: pointer;
86
- margin: var(--file-uploader-li-icon-margin);
87
87
  font-size: var(--icon-size-small);
88
+ margin-left: auto;
88
89
  }
89
90
  li md-icon:hover,
90
91
  li md-icon:active {
@@ -1,7 +1,7 @@
1
1
  import '@material/web/icon/icon.js'
2
2
 
3
3
  import { css, html, nothing } from 'lit'
4
- import { customElement, property } from 'lit/decorators.js'
4
+ import { customElement, property, query, state } from 'lit/decorators.js'
5
5
 
6
6
  import { OxFormField } from './ox-form-field.js'
7
7
 
@@ -10,83 +10,112 @@ export class OxInputSignature extends OxFormField {
10
10
  static styles = [
11
11
  css`
12
12
  :host {
13
- position: relative;
14
- box-sizing: border-box;
15
-
16
13
  display: flex;
17
14
  flex-direction: column;
18
- place-content: center;
19
- border-radius: var(--border-radius);
20
- padding: var(--padding-default, 9px);
21
- min-height: 100px;
22
- text-transform: capitalize;
15
+ min-height: var(--signature-min-height, 80px);
16
+ min-width: var(--signature-min-width, 120px);
23
17
 
24
- border: var(--file-uploader-border);
25
- background-color: var(--md-sys-color-background);
26
- font: var(--file-uploader-font) !important;
27
- color: var(--file-uploader-color);
18
+ background-color: var(--signature-background-color, white);
28
19
 
29
20
  overflow: hidden;
30
21
  }
31
22
 
32
- canvas {
23
+ .signature-preview {
24
+ flex: 1;
25
+ align-self: stretch;
26
+
27
+ border: 1px solid var(--md-sys-color-outline);
28
+ background-size: contain;
29
+ background-repeat: no-repeat;
30
+ background-position: center;
31
+ }
32
+
33
+ dialog canvas {
33
34
  width: 100%;
34
- border: 1px solid #000;
35
+ height: 100%;
36
+ border: 1px solid var(--md-sys-color-outline);
35
37
  }
36
38
 
37
39
  .controls {
38
40
  margin-top: 10px;
41
+ display: flex;
42
+ flex-direction: row;
43
+ gap: var(--spacing-medium);
44
+ }
45
+
46
+ .filler {
47
+ flex: 1;
39
48
  }
40
49
  `
41
50
  ]
42
51
 
43
- @property({ type: Boolean }) isDrawing = false
44
52
  @property({ type: String }) value: string | null = null
45
53
 
46
- private ctx!: CanvasRenderingContext2D
54
+ @query('.signature-preview')
55
+ private previewDiv!: HTMLDivElement
56
+ @query('dialog')
57
+ private dialog!: HTMLDialogElement
58
+ @query('canvas')
47
59
  private canvas!: HTMLCanvasElement
48
60
 
61
+ private ctx!: CanvasRenderingContext2D
62
+ private isDrawing = false
63
+
49
64
  render() {
50
65
  return html`
51
- <canvas
52
- width="400"
53
- height="200"
54
- @mousedown=${this.startDrawing}
55
- @mouseup=${this.stopDrawing}
56
- @mousemove=${this.draw}
57
- @mouseleave=${this.stopDrawing}
58
- @touchstart=${this.startDrawing}
59
- @touchend=${this.stopDrawing}
60
- @touchmove=${this.draw}
61
- ></canvas>
62
-
63
- ${!this.disabled
64
- ? html` <div class="controls">
65
- <button @click="${this.clearCanvas}">Clear</button>
66
- <button @click="${this.saveSignature}">Save</button>
67
- </div>`
68
- : nothing}
66
+ <div class="signature-preview" @click=${this.openDialog}></div>
67
+
68
+ <dialog>
69
+ <canvas
70
+ width="800"
71
+ height="400"
72
+ @mousedown=${this.startDrawing}
73
+ @mouseup=${this.stopDrawing}
74
+ @mousemove=${this.draw}
75
+ @mouseleave=${this.stopDrawing}
76
+ @touchstart=${this.startDrawing}
77
+ @touchend=${this.stopDrawing}
78
+ @touchmove=${this.draw}
79
+ ></canvas>
80
+ <div class="controls">
81
+ <button @click="${this.clearCanvas}">Clear</button>
82
+ <div class="filler"></div>
83
+ <button @click="${this.saveSignature}">Save</button>
84
+ <button @click="${this.closeDialog}">Close</button>
85
+ </div>
86
+ </dialog>
69
87
  `
70
88
  }
71
89
 
72
90
  firstUpdated() {
73
- this.canvas = this.shadowRoot!.querySelector('canvas')!
74
91
  this.ctx = this.canvas.getContext('2d')!
75
92
  this.ctx.strokeStyle = '#000'
76
93
  this.ctx.lineWidth = 2
77
94
 
78
- // 처음 로딩 시 서명 데이터를 캔버스에 표시
95
+ // 처음 로딩 시 서명 데이터를 미리보기 div에 표시
79
96
  if (this.value) {
80
97
  this.loadSignature(this.value)
81
98
  }
82
99
  }
83
100
 
84
- updated(changedProperties: Map<string | number | symbol, unknown>) {
85
- if (changedProperties.has('value') && this.value) {
86
- this.loadSignature(this.value)
101
+ openDialog() {
102
+ if (this.disabled) return
103
+ this.dialog.showModal()
104
+
105
+ // 다이아로그가 열릴 때 현재 value를 캔버스에 그리기
106
+ if (this.value) {
107
+ const img = new Image()
108
+ img.onload = () => {
109
+ this.ctx.drawImage(img, 0, 0, this.canvas.width, this.canvas.height)
110
+ }
111
+ img.src = this.value
87
112
  }
88
113
  }
89
114
 
115
+ closeDialog() {
116
+ this.dialog.close()
117
+ }
118
+
90
119
  startDrawing(event: MouseEvent | TouchEvent) {
91
120
  if (this.disabled) {
92
121
  return
@@ -116,15 +145,12 @@ export class OxInputSignature extends OxFormField {
116
145
  saveSignature() {
117
146
  this.value = this.canvas.toDataURL()
118
147
  this._notifyChange()
148
+ this.previewDiv.style.backgroundImage = `url(${this.value})`
149
+ this.closeDialog()
119
150
  }
120
151
 
121
152
  loadSignature(dataUrl: string) {
122
- const image = new Image()
123
- image.onload = () => {
124
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) // 이전 내용을 지움
125
- this.ctx.drawImage(image, 0, 0) // 이미지를 캔버스에 그림
126
- }
127
- image.src = dataUrl
153
+ this.previewDiv.style.backgroundImage = `url(${dataUrl})`
128
154
  }
129
155
 
130
156
  getEventPosition(event: MouseEvent | TouchEvent) {
@@ -0,0 +1,257 @@
1
+ import { css, html, PropertyValues } from 'lit'
2
+ import { customElement, property, query, state } from 'lit/decorators.js'
3
+ import { OxFormField } from './ox-form-field'
4
+
5
+ type Card = {
6
+ name: string
7
+ image: string
8
+ }
9
+
10
+ @customElement('ox-select-floor')
11
+ export class OxSelectFloor extends OxFormField {
12
+ static styles = [
13
+ css`
14
+ :host {
15
+ display: block;
16
+ position: relative;
17
+ overflow: hidden;
18
+ height: 100%;
19
+
20
+ --ox-select-floor-rotate-x: 60deg;
21
+ --ox-select-floor-rotate-x-active: 40deg;
22
+ --ox-select-floor-perspective: 1200px;
23
+ }
24
+
25
+ .carousel-container {
26
+ position: relative;
27
+ height: 100%;
28
+ width: 100%;
29
+ overflow: hidden;
30
+ user-select: none;
31
+ }
32
+
33
+ .card {
34
+ position: absolute;
35
+ bottom: 0;
36
+ width: 100%;
37
+ background-color: white;
38
+ transition:
39
+ transform 0.3s ease,
40
+ opacity 0.3s ease,
41
+ box-shadow 0.3s ease,
42
+ border 0.3s ease;
43
+ transform-origin: bottom;
44
+ transform: perspective(var(--ox-select-floor-perspective)) rotateX(var(--ox-select-floor-rotate-x));
45
+ opacity: 0.5;
46
+ border: 2px solid transparent;
47
+ border-radius: 12px;
48
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
49
+ }
50
+
51
+ .card img {
52
+ width: 100%;
53
+ height: auto;
54
+ display: block;
55
+ pointer-events: none;
56
+ border-radius: 12px;
57
+ }
58
+
59
+ .selected {
60
+ z-index: 10;
61
+ opacity: 0.8;
62
+ border: 4px solid #3b82f6;
63
+ box-shadow: 0 8px 16px rgba(59, 130, 246, 0.4);
64
+ }
65
+
66
+ .selected.active {
67
+ opacity: 1;
68
+ transform: perspective(var(--ox-select-floor-perspective)) rotateX(var(--ox-select-floor-rotate-x-active));
69
+ box-shadow: 0 12px 24px rgba(59, 130, 246, 0.4);
70
+ }
71
+
72
+ [template-container] {
73
+ position: absolute;
74
+ right: 10px;
75
+ z-index: 20;
76
+ }
77
+ `
78
+ ]
79
+
80
+ @property({ type: Array }) cards: Card[] = []
81
+ @property({ type: String }) value?: string
82
+ @property({ type: Number }) bottomLimit = 70
83
+
84
+ @state() private selectedIndex = 0
85
+ @state() private activeIndex: number | null = null
86
+ @query('.carousel-container') private carouselContainer!: HTMLDivElement
87
+
88
+ private isDragging = false
89
+ private lastTouchY = 0
90
+ private lastMouseY = 0
91
+
92
+ render() {
93
+ const length = this.cards.length
94
+
95
+ return html`
96
+ <div
97
+ class="carousel-container"
98
+ @wheel=${this.handleWheel}
99
+ @mousedown=${this.handleMouseDown}
100
+ @mousemove=${this.handleMouseMove}
101
+ @mouseup=${this.handleMouseUp}
102
+ @mouseleave=${this.handleMouseLeave}
103
+ @touchstart=${this.handleTouchStart}
104
+ @touchmove=${this.handleTouchMove}
105
+ @touchend=${this.handleTouchEnd}
106
+ >
107
+ ${this.cards.map(({ image, name }, index) => {
108
+ const reverse = length - index - 1
109
+ return html`
110
+ <div
111
+ class="card ${this.getClassForCard(reverse)} ${this.isActive(reverse) ? 'active' : ''}"
112
+ style="bottom: ${(this.bottomLimit * reverse) / length}%;"
113
+ @click=${() => this.selectCard(reverse)}
114
+ @tap=${() => this.toggleActiveCard(reverse)}
115
+ >
116
+ <img src="${image}" @load=${(e: Event) => this.adjustCardHeight(e, reverse)} />
117
+ </div>
118
+ `
119
+ })}
120
+ ${this.cards.map(({ image, name }, index) => {
121
+ const reverse = length - index - 1
122
+ return html`
123
+ <div
124
+ style="bottom: ${(this.bottomLimit * reverse) / length}%;"
125
+ @click=${() => this.selectCard(reverse)}
126
+ template-container
127
+ >
128
+ <slot name="template-${reverse}"></slot>
129
+ </div>
130
+ `
131
+ })}
132
+ </div>
133
+ `
134
+ }
135
+
136
+ updated(changes: PropertyValues<this>) {
137
+ if (changes.has('value')) {
138
+ if (this.value) {
139
+ this.selectedIndex = this.cards.findIndex(card => card.name == this.value)
140
+ } else {
141
+ this.selectedIndex = -1
142
+ }
143
+ }
144
+ }
145
+
146
+ firstUpdated() {
147
+ this.scrollToSelectedCard()
148
+ }
149
+
150
+ getClassForCard(index: number) {
151
+ return index === this.selectedIndex ? 'selected' : 'compressed'
152
+ }
153
+
154
+ isActive(index: number): boolean {
155
+ return this.activeIndex === index
156
+ }
157
+
158
+ handleWheel(event: WheelEvent) {
159
+ event.preventDefault()
160
+ const delta = Math.sign(event.deltaY)
161
+ this.updateSelectedIndex(this.selectedIndex + delta)
162
+ }
163
+
164
+ handleTouchStart(event: TouchEvent) {
165
+ this.lastTouchY = event.touches[0].clientY
166
+ }
167
+
168
+ handleTouchMove(event: TouchEvent) {
169
+ const touch = event.touches[0]
170
+ const deltaY = touch.clientY - this.lastTouchY
171
+ this.lastTouchY = touch.clientY
172
+
173
+ if (Math.abs(deltaY) > 30) {
174
+ const direction = deltaY < 0 ? -1 : 1
175
+ this.updateSelectedIndex(this.selectedIndex + direction)
176
+ }
177
+ }
178
+
179
+ handleTouchEnd() {
180
+ this.isDragging = false
181
+ }
182
+
183
+ handleMouseDown(event: MouseEvent) {
184
+ this.isDragging = true
185
+ this.lastMouseY = event.clientY
186
+ }
187
+
188
+ handleMouseMove(event: MouseEvent) {
189
+ if (!this.isDragging) {
190
+ return
191
+ }
192
+
193
+ const deltaY = event.clientY - this.lastMouseY
194
+
195
+ if (!this.lastMouseY) {
196
+ this.lastMouseY = event.clientY
197
+ }
198
+
199
+ if (Math.abs(deltaY) > 30) {
200
+ this.lastMouseY = event.clientY
201
+ const direction = deltaY > 0 ? -1 : 1
202
+ this.updateSelectedIndex(this.selectedIndex + direction)
203
+ }
204
+ }
205
+
206
+ handleMouseUp() {
207
+ this.isDragging = false
208
+ }
209
+
210
+ handleMouseLeave() {
211
+ this.isDragging = false
212
+ }
213
+
214
+ private toggleActiveCard(index: number) {
215
+ if (this.activeIndex === index) {
216
+ this.activeIndex = null
217
+ } else {
218
+ this.activeIndex = index
219
+ }
220
+ }
221
+
222
+ private updateSelectedIndex(newIndex: number) {
223
+ this.activeIndex = null
224
+
225
+ this.selectedIndex = Math.max(0, Math.min(newIndex, this.cards.length - 1))
226
+ this.scrollToSelectedCard()
227
+ }
228
+
229
+ private scrollToSelectedCard() {
230
+ const cardHeight = 320
231
+ const targetScrollTop = this.selectedIndex * cardHeight - (window.innerHeight / 2 - cardHeight / 2)
232
+ this.carouselContainer.scrollTo({ top: targetScrollTop, behavior: 'smooth' })
233
+ }
234
+
235
+ private selectCard(index: number) {
236
+ this.selectedIndex = index
237
+ this._notifySelection()
238
+ this.scrollToSelectedCard()
239
+ }
240
+
241
+ private _notifySelection() {
242
+ this.value = this.selectedIndex !== -1 ? this.cards[this.selectedIndex]?.name : undefined
243
+
244
+ this.dispatchEvent(
245
+ new CustomEvent('change', {
246
+ detail: this.value
247
+ })
248
+ )
249
+ }
250
+
251
+ private adjustCardHeight(e: Event, index: number) {
252
+ const imgElement = e.target as HTMLImageElement
253
+ const aspectRatio = imgElement.naturalWidth / imgElement.naturalHeight
254
+ const newHeight = imgElement.offsetWidth / aspectRatio
255
+ imgElement.style.height = `${newHeight}px`
256
+ }
257
+ }