@operato/input 8.0.0-alpha.4 → 8.0.0-alpha.45

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 (51) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/dist/src/index.d.ts +1 -0
  3. package/dist/src/index.js +1 -0
  4. package/dist/src/index.js.map +1 -1
  5. package/dist/src/ox-input-color-stops.d.ts +1 -1
  6. package/dist/src/ox-input-color-stops.js +2 -2
  7. package/dist/src/ox-input-color-stops.js.map +1 -1
  8. package/dist/src/ox-input-direction.d.ts +1 -0
  9. package/dist/src/ox-input-direction.js +37 -4
  10. package/dist/src/ox-input-direction.js.map +1 -1
  11. package/dist/src/ox-input-signature.d.ts +4 -2
  12. package/dist/src/ox-input-signature.js +34 -14
  13. package/dist/src/ox-input-signature.js.map +1 -1
  14. package/dist/src/ox-input-switch.d.ts +9 -0
  15. package/dist/src/ox-input-switch.js +121 -0
  16. package/dist/src/ox-input-switch.js.map +1 -0
  17. package/dist/src/ox-select-floor.d.ts +35 -0
  18. package/dist/src/ox-select-floor.js +238 -0
  19. package/dist/src/ox-select-floor.js.map +1 -0
  20. package/dist/stories/image-for-select-floor.d.ts +1 -0
  21. package/dist/stories/image-for-select-floor.js +2 -0
  22. package/dist/stories/image-for-select-floor.js.map +1 -0
  23. package/dist/stories/ox-input-direction.stories.js +11 -0
  24. package/dist/stories/ox-input-direction.stories.js.map +1 -1
  25. package/dist/stories/ox-input-switch.stories.d.ts +38 -0
  26. package/dist/stories/ox-input-switch.stories.js +68 -0
  27. package/dist/stories/ox-input-switch.stories.js.map +1 -0
  28. package/dist/stories/ox-select-floor.stories.d.ts +45 -0
  29. package/dist/stories/ox-select-floor.stories.js +166 -0
  30. package/dist/stories/ox-select-floor.stories.js.map +1 -0
  31. package/dist/tsconfig.tsbuildinfo +1 -1
  32. package/package.json +23 -15
  33. package/src/index.ts +1 -0
  34. package/src/ox-input-color-stops.ts +2 -2
  35. package/src/ox-input-direction.ts +31 -4
  36. package/src/ox-input-signature.ts +45 -17
  37. package/src/ox-input-switch.ts +116 -0
  38. package/src/ox-select-floor.ts +246 -0
  39. package/stories/image-for-select-floor.ts +2 -0
  40. package/stories/ox-input-direction.stories.ts +11 -0
  41. package/stories/ox-input-switch.stories.ts +91 -0
  42. package/stories/ox-select-floor.stories.ts +197 -0
  43. package/themes/calendar-theme.css +3 -1
  44. package/assets/images/icon-editor-gradient-direction.png +0 -0
  45. package/assets/images/icon-properties-label.png +0 -0
  46. package/assets/images/icon-properties-line-type.png +0 -0
  47. package/assets/images/icon-properties-table.png +0 -0
  48. package/dist/src/ox-zoomable-image.d.ts +0 -17
  49. package/dist/src/ox-zoomable-image.js +0 -80
  50. package/dist/src/ox-zoomable-image.js.map +0 -1
  51. package/src/ox-zoomable-image.ts +0 -75
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.4",
5
+ "version": "8.0.0-alpha.45",
6
6
  "main": "dist/src/index.js",
7
7
  "module": "dist/src/index.js",
8
8
  "license": "MIT",
@@ -42,6 +42,7 @@
42
42
  "./ox-input-key-values.js": "./dist/src/ox-input-key-values.js",
43
43
  "./ox-select.js": "./dist/src/ox-select.js",
44
44
  "./ox-input-file.js": "./dist/src/ox-input-file.js",
45
+ "./ox-input-switch.js": "./dist/src/ox-input-switch.js",
45
46
  "./ox-input-image.js": "./dist/src/ox-input-image.js",
46
47
  "./ox-input-options.js": "./dist/src/ox-input-options.js",
47
48
  "./ox-input-select-buttons.js": "./dist/src/ox-input-select-buttons.js",
@@ -58,6 +59,7 @@
58
59
  "./ox-input-textarea.js": "./dist/src/ox-input-textarea.js",
59
60
  "./ox-input-direction.js": "./dist/src/ox-input-direction.js",
60
61
  "./ox-input-signature.js": "./dist/src/ox-input-signature.js",
62
+ "./ox-select-floor.js": "./dist/src/ox-select-floor.js",
61
63
  "./ox-input-table-column-config.js": "./dist/src/ox-input-table-column-config.js"
62
64
  },
63
65
  "typesVersions": {
@@ -131,6 +133,9 @@
131
133
  "./ox-input-file.js": [
132
134
  "./dist/src/ox-input-file.d.ts"
133
135
  ],
136
+ "./ox-input-switch.js": [
137
+ "./dist/src/ox-input-switch.d.ts"
138
+ ],
134
139
  "./ox-input-image.js": [
135
140
  "./dist/src/ox-input-image.d.ts"
136
141
  ],
@@ -179,6 +184,9 @@
179
184
  "./ox-input-signature.js": [
180
185
  "./dist/src/ox-input-signature.d.ts"
181
186
  ],
187
+ "./ox-select-floor.js": [
188
+ "./dist/src/ox-select-floor.d.ts"
189
+ ],
182
190
  "./ox-input-table-column-config.js": [
183
191
  "./dist/src/ox-input-table-column-config.d.ts"
184
192
  ]
@@ -210,11 +218,11 @@
210
218
  "@ctrl/tinycolor": "^4.1.0",
211
219
  "@lit/localize": "^0.12.1",
212
220
  "@material/web": "^2.0.0",
213
- "@operato/color-picker": "^8.0.0-alpha.0",
214
- "@operato/i18n": "^8.0.0-alpha.0",
215
- "@operato/popup": "^8.0.0-alpha.4",
216
- "@operato/styles": "^8.0.0-alpha.4",
217
- "@operato/utils": "^8.0.0-alpha.0",
221
+ "@operato/color-picker": "^8.0.0-alpha.33",
222
+ "@operato/i18n": "^8.0.0-alpha.37",
223
+ "@operato/popup": "^8.0.0-alpha.41",
224
+ "@operato/styles": "^8.0.0-alpha.37",
225
+ "@operato/utils": "^8.0.0-alpha.37",
218
226
  "@polymer/paper-dropdown-menu": "^3.2.0",
219
227
  "@polymer/paper-item": "^3.0.1",
220
228
  "@thebespokepixel/es-tinycolor": "^3.1.0",
@@ -225,18 +233,18 @@
225
233
  "lodash-es": "^4.17.21"
226
234
  },
227
235
  "devDependencies": {
228
- "@custom-elements-manifest/analyzer": "^0.9.2",
236
+ "@custom-elements-manifest/analyzer": "^0.10.0",
229
237
  "@hatiolab/prettier-config": "^1.0.0",
230
238
  "@lit/localize-tools": "^0.7.2",
231
239
  "@open-wc/eslint-config": "^12.0.3",
232
- "@open-wc/testing": "^3.1.6",
233
- "@typescript-eslint/eslint-plugin": "^7.0.1",
234
- "@typescript-eslint/parser": "^7.0.1",
235
- "@web/dev-server": "^0.3.0",
240
+ "@open-wc/testing": "^4.0.0",
241
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
242
+ "@typescript-eslint/parser": "^8.0.0",
243
+ "@web/dev-server": "^0.4.0",
236
244
  "@web/dev-server-storybook": "^2.0.1",
237
- "@web/test-runner": "^0.18.0",
238
- "concurrently": "^8.0.1",
239
- "eslint": "^8.39.0",
245
+ "@web/test-runner": "^0.19.0",
246
+ "concurrently": "^9.0.0",
247
+ "eslint": "^9.0.0",
240
248
  "eslint-config-prettier": "^9.1.0",
241
249
  "husky": "^9.0.11",
242
250
  "lint-staged": "^15.2.2",
@@ -258,5 +266,5 @@
258
266
  "prettier --write"
259
267
  ]
260
268
  },
261
- "gitHead": "ac097b448ea96721b3418132e92988afdf764519"
269
+ "gitHead": "4099b112eea6e2c554d1d0a74e7b026f377bb039"
262
270
  }
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ export * from './ox-form-field.js'
6
6
  export * from './ox-buttons-radio.js'
7
7
  export * from './ox-checkbox.js'
8
8
  export * from './ox-select.js'
9
+ export * from './ox-input-switch.js'
9
10
  export * from './ox-input-angle.js'
10
11
  export * from './ox-input-3dish.js'
11
12
  export * from './ox-input-3axis.js'
@@ -210,7 +210,7 @@ export class OxInputColorStops extends OxFormField {
210
210
  <div
211
211
  id="markers"
212
212
  @dblclick=${(e: MouseEvent) => this._onDblClickMarkers(e)}
213
- @mousedown=${(e: MouseEvent) => this._onMouseDown(e)}
213
+ @pointerdown=${(e: PointerEvent) => this._onPointerDown(e)}
214
214
  @dragstart=${(e: DragEvent) => this._onDragStart(e)}
215
215
  @drag=${this._throttled(100, this._onDrag.bind(this))}
216
216
  @dragend=${(e: DragEvent) => this._onDragEnd(e)}
@@ -422,7 +422,7 @@ export class OxInputColorStops extends OxFormField {
422
422
  this.colorEditor.showPicker()
423
423
  }
424
424
 
425
- _onMouseDown(e: MouseEvent) {
425
+ _onPointerDown(e: PointerEvent) {
426
426
  if (this.disabled) {
427
427
  return
428
428
  }
@@ -31,19 +31,46 @@ export class OxInputDirection extends OxFormField {
31
31
  color: var(--md-sys-color-on-primary);
32
32
  background-color: var(--md-sys-color-primary);
33
33
  }
34
+
35
+ /* 아이콘 회전 */
36
+ md-icon[data-value='W'] {
37
+ transform: rotate(-90deg);
38
+ }
39
+
40
+ md-icon[data-value='N'] {
41
+ transform: rotate(0deg);
42
+ }
43
+
44
+ md-icon[data-value='S'] {
45
+ transform: rotate(180deg);
46
+ }
47
+
48
+ md-icon[data-value='E'] {
49
+ transform: rotate(90deg);
50
+ }
34
51
  `
35
52
 
36
53
  @property({ type: String }) value?: string
54
+ @property({ type: Boolean }) inbound?: boolean = false
37
55
 
38
56
  render() {
39
57
  const value = this.value
40
58
 
41
59
  return html`
42
60
  <div @click=${this.onClick.bind(this)}>
43
- <md-icon ?selected=${value == 'W'} data-value="W">arrow_back</md-icon>
44
- <md-icon ?selected=${value == 'N'} data-value="N">arrow_upward</md-icon>
45
- <md-icon ?selected=${value == 'S'} data-value="S">arrow_downward</md-icon>
46
- <md-icon ?selected=${value == 'E'} data-value="E">arrow_forward</md-icon>
61
+ ${this.inbound
62
+ ? html`
63
+ <md-icon ?selected=${value == 'W'} data-value="W">step_into</md-icon>
64
+ <md-icon ?selected=${value == 'N'} data-value="N">step_into</md-icon>
65
+ <md-icon ?selected=${value == 'S'} data-value="S">step_into</md-icon>
66
+ <md-icon ?selected=${value == 'E'} data-value="E">step_into</md-icon>
67
+ `
68
+ : html`
69
+ <md-icon ?selected=${value == 'W'} data-value="W">step_out</md-icon>
70
+ <md-icon ?selected=${value == 'N'} data-value="N">step_out</md-icon>
71
+ <md-icon ?selected=${value == 'S'} data-value="S">step_out</md-icon>
72
+ <md-icon ?selected=${value == 'E'} data-value="E">step_out</md-icon>
73
+ `}
47
74
  </div>
48
75
  `
49
76
  }
@@ -1,7 +1,7 @@
1
1
  import '@material/web/icon/icon.js'
2
2
 
3
- import { css, html, nothing } from 'lit'
4
- import { customElement, property, query, state } from 'lit/decorators.js'
3
+ import { css, html, PropertyValues } from 'lit'
4
+ import { customElement, property, query } from 'lit/decorators.js'
5
5
 
6
6
  import { OxFormField } from './ox-form-field.js'
7
7
 
@@ -31,8 +31,6 @@ export class OxInputSignature extends OxFormField {
31
31
  }
32
32
 
33
33
  dialog canvas {
34
- width: 100%;
35
- height: 100%;
36
34
  border: 1px solid var(--md-sys-color-outline);
37
35
  }
38
36
 
@@ -46,6 +44,19 @@ export class OxInputSignature extends OxFormField {
46
44
  .filler {
47
45
  flex: 1;
48
46
  }
47
+
48
+ /* 버튼 스타일 */
49
+ button {
50
+ background-color: var(--md-sys-color-primary);
51
+ color: var(--md-sys-color-on-primary);
52
+ padding: 10px 20px;
53
+ border: none;
54
+ border-radius: var(--spacing-small);
55
+ font-family: 'Roboto', sans-serif;
56
+ font-size: 14px;
57
+ cursor: pointer;
58
+ transition: background-color 0.3s ease;
59
+ }
49
60
  `
50
61
  ]
51
62
 
@@ -69,13 +80,10 @@ export class OxInputSignature extends OxFormField {
69
80
  <canvas
70
81
  width="800"
71
82
  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}
83
+ @pointerdown=${this.startDrawing}
84
+ @pointerup=${this.stopDrawing}
85
+ @pointermove=${this.draw}
86
+ @pointerleave=${this.stopDrawing}
79
87
  ></canvas>
80
88
  <div class="controls">
81
89
  <button @click="${this.clearCanvas}">Clear</button>
@@ -98,11 +106,19 @@ export class OxInputSignature extends OxFormField {
98
106
  }
99
107
  }
100
108
 
109
+ updated(changes: PropertyValues<this>) {
110
+ if (changes.has('value')) {
111
+ this.loadSignature(this.value)
112
+ }
113
+ }
114
+
101
115
  openDialog() {
102
- if (this.disabled) return
116
+ if (this.disabled) {
117
+ return
118
+ }
119
+
103
120
  this.dialog.showModal()
104
121
 
105
- // 다이아로그가 열릴 때 현재 value를 캔버스에 그리기
106
122
  if (this.value) {
107
123
  const img = new Image()
108
124
  img.onload = () => {
@@ -121,18 +137,30 @@ export class OxInputSignature extends OxFormField {
121
137
  return
122
138
  }
123
139
 
140
+ event.preventDefault()
141
+ event.stopPropagation()
142
+
124
143
  this.isDrawing = true
125
144
  this.ctx.beginPath()
126
145
  const position = this.getEventPosition(event)
127
146
  this.ctx.moveTo(position.x, position.y)
128
147
  }
129
148
 
130
- stopDrawing() {
149
+ stopDrawing(event: MouseEvent | TouchEvent) {
150
+ event.preventDefault()
151
+ event.stopPropagation()
152
+
131
153
  this.isDrawing = false
132
154
  }
133
155
 
134
156
  draw(event: MouseEvent | TouchEvent) {
135
- if (!this.isDrawing) return
157
+ if (!this.isDrawing) {
158
+ return
159
+ }
160
+
161
+ event.preventDefault()
162
+ event.stopPropagation()
163
+
136
164
  const position = this.getEventPosition(event)
137
165
  this.ctx.lineTo(position.x, position.y)
138
166
  this.ctx.stroke()
@@ -149,8 +177,8 @@ export class OxInputSignature extends OxFormField {
149
177
  this.closeDialog()
150
178
  }
151
179
 
152
- loadSignature(dataUrl: string) {
153
- this.previewDiv.style.backgroundImage = `url(${dataUrl})`
180
+ loadSignature(dataUrl: string | null) {
181
+ this.previewDiv.style.backgroundImage = dataUrl ? `url(${dataUrl})` : 'none'
154
182
  }
155
183
 
156
184
  getEventPosition(event: MouseEvent | TouchEvent) {
@@ -0,0 +1,116 @@
1
+ import { css, html } from 'lit'
2
+ import { customElement, property } from 'lit/decorators.js'
3
+
4
+ import { OxFormField } from './ox-form-field'
5
+
6
+ @customElement('ox-input-switch')
7
+ export class OxInputSwitch extends OxFormField {
8
+ static styles = css`
9
+ :host {
10
+ --ox-simple-switch-fullwidth: 2em;
11
+ --ox-simple-switch-fullheight: 1em;
12
+ --ox-simple-switch-thumbnail-size: 1em;
13
+ }
14
+
15
+ label {
16
+ position: relative;
17
+ display: inline-block;
18
+ width: 100%;
19
+ height: 100%;
20
+ }
21
+
22
+ label input {
23
+ opacity: 0;
24
+ width: 0;
25
+ height: 0;
26
+ }
27
+
28
+ span {
29
+ position: absolute;
30
+ cursor: pointer;
31
+ width: var(--ox-simple-switch-fullwidth);
32
+ height: var(--ox-simple-switch-fullheight);
33
+ top: calc(0 - var(--ox-simple-switch-thumbnail-size));
34
+ left: 0;
35
+ background-color: var(--ox-simple-switch-off-color, #ccc);
36
+ -webkit-transition: 0.4s;
37
+ transition: 0.4s;
38
+ }
39
+
40
+ span:before {
41
+ position: absolute;
42
+ content: '';
43
+ height: calc(var(--ox-simple-switch-thumbnail-size) - 8px);
44
+ width: calc(var(--ox-simple-switch-thumbnail-size) - 8px);
45
+ left: 4px;
46
+ top: 4px;
47
+ background-color: var(--ox-simple-switch-thumbnail-color, white);
48
+ -webkit-transition: 0.4s;
49
+ transition: 0.4s;
50
+ }
51
+
52
+ input:checked + span {
53
+ background-color: var(--ox-simple-switch-on-color, #2196f3);
54
+ }
55
+
56
+ input + span:before {
57
+ -webkit-transform: translateY(
58
+ calc((var(--ox-simple-switch-fullheight) - var(--ox-simple-switch-thumbnail-size)) / 2)
59
+ );
60
+ -ms-transform: translateY(
61
+ calc((var(--ox-simple-switch-fullheight) - var(--ox-simple-switch-thumbnail-size)) / 2)
62
+ );
63
+ transform: translateY(calc((var(--ox-simple-switch-fullheight) - var(--ox-simple-switch-thumbnail-size)) / 2));
64
+ }
65
+
66
+ input:checked + span:before {
67
+ -webkit-transform: translateX(calc(var(--ox-simple-switch-fullwidth) - var(--ox-simple-switch-thumbnail-size)))
68
+ translateY(calc((var(--ox-simple-switch-fullheight) - var(--ox-simple-switch-thumbnail-size)) / 2));
69
+ -ms-transform: translateX(calc(var(--ox-simple-switch-fullwidth) - var(--ox-simple-switch-thumbnail-size)))
70
+ translateY(calc((var(--ox-simple-switch-fullheight) - var(--ox-simple-switch-thumbnail-size)) / 2));
71
+ transform: translateX(calc(var(--ox-simple-switch-fullwidth) - var(--ox-simple-switch-thumbnail-size)))
72
+ translateY(calc((var(--ox-simple-switch-fullheight) - var(--ox-simple-switch-thumbnail-size)) / 2));
73
+ }
74
+
75
+ /* Rounded sliders */
76
+ span[round] {
77
+ border-radius: calc(var(--ox-simple-switch-thumbnail-size) / 2);
78
+ }
79
+
80
+ span[round]:before {
81
+ border-radius: calc((var(--ox-simple-switch-thumbnail-size) - 8px) / 2);
82
+ }
83
+ `
84
+
85
+ @property({ type: Boolean }) round: boolean = false
86
+ @property({ type: Boolean }) value: boolean = false
87
+
88
+ render() {
89
+ return html`
90
+ <label>
91
+ <input type="checkbox" .checked=${this.value} />
92
+ <span ?round=${this.round}></span>
93
+ </label>
94
+ `
95
+ }
96
+
97
+ firstUpdated() {
98
+ this.renderRoot.addEventListener('change', (e: Event) => {
99
+ e.preventDefault()
100
+ e.stopPropagation()
101
+
102
+ this.value = (e.target as HTMLInputElement)?.checked
103
+ this.dispatchEvent(
104
+ new CustomEvent('change', {
105
+ bubbles: true,
106
+ composed: true,
107
+ detail: this.value
108
+ })
109
+ )
110
+ })
111
+ }
112
+
113
+ protected appendFormData({ formData }: FormDataEvent): void {
114
+ this.name && formData.append(this.name, this.value ? 'true' : 'false')
115
+ }
116
+ }
@@ -0,0 +1,246 @@
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
+ width: 100%;
28
+ overflow: hidden;
29
+ user-select: none;
30
+ }
31
+
32
+ .card {
33
+ position: absolute;
34
+ bottom: 0;
35
+ width: 100%;
36
+ background-color: white;
37
+ transition:
38
+ transform 0.3s ease,
39
+ opacity 0.3s ease,
40
+ box-shadow 0.3s ease,
41
+ border 0.3s ease;
42
+ transform-origin: bottom;
43
+ transform: perspective(var(--ox-select-floor-perspective)) rotateX(var(--ox-select-floor-rotate-x));
44
+ opacity: 0.5;
45
+ border: 2px solid transparent;
46
+ border-radius: 12px;
47
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
48
+ }
49
+
50
+ .card img {
51
+ width: 100%;
52
+ height: auto;
53
+ display: block;
54
+ pointer-events: none;
55
+ border-radius: 12px;
56
+ }
57
+
58
+ .selected {
59
+ opacity: 0.8;
60
+ z-index: 1;
61
+ border: 4px solid #3b82f6;
62
+ box-shadow: 0 8px 16px rgba(59, 130, 246, 0.4);
63
+ }
64
+
65
+ .selected.active {
66
+ opacity: 1;
67
+ z-index: 2;
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: 1;
76
+ }
77
+ `
78
+ ]
79
+
80
+ @property({ type: Array }) cards: Card[] = []
81
+ @property({ type: String }) value?: string
82
+ @property({ type: Number }) bottomLimit = 70
83
+ @property({ type: Number }) interval = 0
84
+
85
+ @state() private selectedIndex = -1
86
+ @state() private activeIndex: number | null = null
87
+ @query('.carousel-container') private carouselContainer!: HTMLDivElement
88
+
89
+ private isDragging = false
90
+ private lastMouseY = 0
91
+
92
+ render() {
93
+ const length = this.cards.length
94
+ const cards = this.cards
95
+ const interval = this.interval
96
+
97
+ return html`
98
+ <div
99
+ class="carousel-container"
100
+ @wheel=${interval ? () => {} : this.handleWheel}
101
+ @pointerdown=${this.handlePointerDown}
102
+ @pointermove=${this.handlePointerMove}
103
+ @pointerup=${this.handlePointerUp}
104
+ @pointerleave=${this.handlePointerLeave}
105
+ style=${interval ? `height: ${interval * length + 200}px;` : 'height: 100%;'}
106
+ >
107
+ ${cards.map(({ image, name }, index) => {
108
+ return html`
109
+ <div
110
+ class="card ${this.getClassForCard(index)} ${this.isActive(index) ? 'active' : ''}"
111
+ style=${`bottom: ${interval ? interval * index + 'px' : (this.bottomLimit * index) / length + '%'};`}
112
+ @click=${() => {
113
+ this.selectCard(index)
114
+ this.toggleActiveCard(index)
115
+ }}
116
+ >
117
+ <img src="${image}" @load=${(e: Event) => this.adjustCardHeight(e, index)} />
118
+ </div>
119
+ `
120
+ })}
121
+ ${cards.map(({ image, name }, index) => {
122
+ return html`
123
+ <div
124
+ style=${`bottom: ${interval ? interval * index + 'px' : (this.bottomLimit * index) / length + '%'};`}
125
+ @click=${() => this.selectCard(index)}
126
+ template-container
127
+ >
128
+ <slot name="template-${index}"></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
+ handlePointerDown(event: PointerEvent) {
165
+ event.preventDefault()
166
+
167
+ this.isDragging = true
168
+ this.lastMouseY = event.clientY
169
+ }
170
+
171
+ handlePointerMove(event: PointerEvent) {
172
+ if (!this.isDragging) {
173
+ return
174
+ }
175
+
176
+ event.preventDefault()
177
+
178
+ const deltaY = event.clientY - this.lastMouseY
179
+
180
+ if (!this.lastMouseY) {
181
+ this.lastMouseY = event.clientY
182
+ }
183
+
184
+ if (Math.abs(deltaY) > 30) {
185
+ this.lastMouseY = event.clientY
186
+ const direction = deltaY > 0 ? -1 : 1
187
+ this.updateSelectedIndex(this.selectedIndex + direction)
188
+ }
189
+ }
190
+
191
+ handlePointerUp(event: PointerEvent) {
192
+ event.preventDefault()
193
+ this.isDragging = false
194
+ }
195
+
196
+ handlePointerLeave(event: PointerEvent) {
197
+ event.preventDefault()
198
+
199
+ this.isDragging = false
200
+ }
201
+
202
+ private toggleActiveCard(index: number) {
203
+ if (this.activeIndex === index) {
204
+ this.activeIndex = null
205
+ } else {
206
+ this.activeIndex = index
207
+ }
208
+ }
209
+
210
+ private updateSelectedIndex(newIndex: number) {
211
+ this.activeIndex = null
212
+
213
+ this.selectedIndex = Math.max(-1, Math.min(newIndex, this.cards.length - 1))
214
+ this.scrollToSelectedCard()
215
+ }
216
+
217
+ private scrollToSelectedCard() {
218
+ const cardHeight = 320
219
+ const targetScrollTop = this.selectedIndex * cardHeight - (window.innerHeight / 2 - cardHeight / 2)
220
+
221
+ this.carouselContainer.scrollTo({ top: targetScrollTop, behavior: 'smooth' })
222
+ }
223
+
224
+ private selectCard(index: number) {
225
+ this.selectedIndex = index
226
+ this.notifySelection()
227
+ this.scrollToSelectedCard()
228
+ }
229
+
230
+ private notifySelection() {
231
+ this.value = this.selectedIndex !== -1 ? this.cards[this.selectedIndex]?.name : undefined
232
+
233
+ this.dispatchEvent(
234
+ new CustomEvent('change', {
235
+ detail: this.value
236
+ })
237
+ )
238
+ }
239
+
240
+ private adjustCardHeight(e: Event, index: number) {
241
+ const imgElement = e.target as HTMLImageElement
242
+ const aspectRatio = imgElement.naturalWidth / imgElement.naturalHeight
243
+ const newHeight = imgElement.offsetWidth / aspectRatio
244
+ imgElement.style.height = `${newHeight}px`
245
+ }
246
+ }