x4js 2.3.27 → 2.3.29

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 (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/aicontext.md +412 -24
  4. package/package.json +8 -3
  5. package/src/components/autocomplete/autocomplete.ts +5 -1
  6. package/src/components/bargraph/bargraph.module.scss +44 -0
  7. package/src/components/bargraph/bargraph.ts +362 -0
  8. package/src/components/base.scss +71 -1
  9. package/src/components/boxes/boxes.ts +2 -2
  10. package/src/components/button/button.module.scss +4 -3
  11. package/src/components/calendar/calendar.module.scss +6 -5
  12. package/src/components/calendar/calendar.ts +14 -8
  13. package/src/components/canvas/canvas.ts +5 -4
  14. package/src/components/carousel/carousel.module.scss +165 -0
  15. package/src/components/carousel/carousel.ts +423 -0
  16. package/src/components/chart/chart.module.scss +8 -0
  17. package/src/components/chart/chart.ts +170 -12
  18. package/src/components/checkbox/checkbox.module.scss +67 -1
  19. package/src/components/checkbox/checkbox.ts +22 -6
  20. package/src/components/colorinput/colorinput.module.scss +1 -0
  21. package/src/components/combobox/combobox.module.scss +6 -2
  22. package/src/components/components.ts +3 -0
  23. package/src/components/gantt/gantt.module.scss +462 -0
  24. package/src/components/gantt/gantt.ts +1254 -0
  25. package/src/components/gridview/gridview.module.scss +6 -0
  26. package/src/components/input/input.module.scss +1 -1
  27. package/src/components/knob/knob.module.scss +113 -0
  28. package/src/components/knob/knob.ts +355 -0
  29. package/src/components/mapview/mapview.module.scss +177 -0
  30. package/src/components/mapview/mapview.ts +764 -0
  31. package/src/components/messages/messages.module.scss +9 -4
  32. package/src/components/planner/planner.module.scss +688 -0
  33. package/src/components/planner/planner.ts +1792 -0
  34. package/src/components/propgrid/progrid.module.scss +13 -5
  35. package/src/components/radio/radio.module.scss +58 -32
  36. package/src/components/radio/radio.ts +12 -1
  37. package/src/components/sizers/sizer.ts +17 -5
  38. package/src/components/slider/slider.module.scss +22 -20
  39. package/src/components/spreadsheet/spreadsheet.module.scss +6 -0
  40. package/src/components/switch/switch.module.scss +68 -44
  41. package/src/components/switch/switch.ts +6 -3
  42. package/src/components/tabs/tabs.module.scss +2 -0
  43. package/src/components/tabs/tabs.ts +1 -0
  44. package/src/components/tag/tag.module.scss +4 -4
  45. package/src/components/textarea/textarea.module.scss +9 -0
  46. package/src/components/textarea/textarea.ts +1 -0
  47. package/src/components/textedit/textedit.module.scss +5 -2
  48. package/src/components/themes.scss +20 -6
  49. package/src/components/treeview/treeview.module.scss +6 -1
  50. package/src/components/treeview/treeview.ts +1 -0
  51. package/src/components/video/video.module.scss +5 -2
  52. package/src/components/video/video.ts +14 -1
  53. package/src/core/core_i18n.ts +65 -0
  54. package/src/core/core_pdf.ts +17 -0
  55. package/src/core/core_react.ts +12 -18
  56. package/src/core/core_svg.ts +9 -6
  57. package/src/core/core_tools.ts +44 -0
  58. package/src/x4doc.ts +3 -0
@@ -73,6 +73,11 @@
73
73
 
74
74
  background-color: var( --gridview-background );
75
75
  border: 1px solid var( --gridview-border );
76
+
77
+ // disabled: faded as a Box (a Gridview is not one)
78
+ &[disabled] {
79
+ opacity: var( --box-disabled-opacity );
80
+ }
76
81
  color: var( --gridview-cell-color );
77
82
 
78
83
  .cell {
@@ -159,6 +164,7 @@
159
164
  }
160
165
 
161
166
  .percent {
167
+ align-self: center; // a Box stretches (top of the cell): centered in the row
162
168
  width: 100%;
163
169
  border: 1px solid var( --border );
164
170
  height: 8px;
@@ -20,7 +20,7 @@
20
20
 
21
21
  --input-sel-background: var( --accent-background );
22
22
  --input-sel-color: var( --accent-color );
23
- --input-placeholder: var( --disabled-background );
23
+ --input-placeholder: var( --disabled-color-dark ); // a text colour: seen on the background of the field, in both themes
24
24
  --input-error: var( --alert-background );
25
25
 
26
26
  --input-color-disabled: var( --disabled-color-dark );
@@ -0,0 +1,113 @@
1
+ /**
2
+ * ___ ___ __
3
+ * \ \/ / / _
4
+ * \ / /_| |_
5
+ * / \____ _|
6
+ * /__/\__\ |_|
7
+ *
8
+ * @file knob.module.scss
9
+ * @author Etienne Cochard
10
+ *
11
+ * @copyright (c) 2026 R-libre ingenierie
12
+ *
13
+ * Use of this source code is governed by an MIT-style license
14
+ * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
+ **/
16
+
17
+ :root {
18
+ --knob-track: var( --border );
19
+ --knob-value: var( --accent-background );
20
+ --knob-cap-background: var( --background-secondary );
21
+ --knob-cap-border: var( --border );
22
+ --knob-pointer: var( --accent-background );
23
+ --knob-border-focus: var( --border-focus );
24
+ --knob-track-disabled: color-mix( in srgb, var( --disabled-color-light ) 50%, var( --background-primary ) );
25
+ --knob-value-disabled: var( --disabled-color-light );
26
+ --knob-cap-background-disabled: var( --background-primary );
27
+ }
28
+
29
+ .x4knob {
30
+ display: flex;
31
+ flex-direction: column;
32
+ align-items: center;
33
+ gap: 4px;
34
+ flex-shrink: 0;
35
+ outline: none;
36
+
37
+ // the round: its diameter in --knob-size (the prop size)
38
+ .dial {
39
+ position: relative;
40
+ flex-shrink: 0;
41
+ width: var( --knob-size, 48px );
42
+ height: var( --knob-size, 48px );
43
+ border-radius: 50%;
44
+ touch-action: none;
45
+ cursor: grab;
46
+ }
47
+
48
+ &.dragging .dial {
49
+ cursor: grabbing;
50
+ }
51
+
52
+ // range: the arc of 270° from -135°, the value in --knob-perc (set by the code)
53
+ .ring {
54
+ position: absolute;
55
+ inset: 0;
56
+ border-radius: 50%;
57
+
58
+ background: conic-gradient( from -135deg,
59
+ var( --knob-value ) 0 var( --knob-perc, 0deg ),
60
+ var( --knob-track ) var( --knob-perc, 0deg ) 270deg,
61
+ transparent 270deg );
62
+
63
+ // a ring of 4px
64
+ mask: radial-gradient( farthest-side, transparent calc( 100% - 4px ), #000 calc( 100% - 3.5px ) );
65
+ }
66
+
67
+ .cap {
68
+ position: absolute;
69
+ inset: 8px;
70
+ border-radius: 50%;
71
+ background-color: var( --knob-cap-background );
72
+ border: 1px solid var( --knob-cap-border );
73
+ transform: rotate( var( --knob-angle, 0deg ) );
74
+ }
75
+
76
+ .pointer {
77
+ position: absolute;
78
+ left: 50%;
79
+ top: 3px;
80
+ width: 3px;
81
+ height: 30%;
82
+ border-radius: 2px;
83
+ background-color: var( --knob-pointer );
84
+ transform: translateX( -50% );
85
+ }
86
+
87
+ // infinite: no start nor end, ticks all around
88
+ &.infinite .ring {
89
+ background: repeating-conic-gradient( from -1.5deg,
90
+ var( --knob-track ) 0 3deg,
91
+ transparent 3deg 30deg );
92
+ }
93
+
94
+ // the colors of the knob take the disabled ones
95
+ &[disabled] {
96
+ --knob-track: var( --knob-track-disabled );
97
+ --knob-value: var( --knob-value-disabled );
98
+ --knob-pointer: var( --knob-value-disabled );
99
+ --knob-cap-background: var( --knob-cap-background-disabled );
100
+ --knob-cap-border: var( --knob-track-disabled );
101
+
102
+ &, .dial {
103
+ cursor: not-allowed;
104
+ }
105
+ }
106
+
107
+ &:focus .cap {
108
+ outline-offset: 2px;
109
+ outline-color: var( --knob-border-focus );
110
+ outline-style: solid;
111
+ outline-width: 1px;
112
+ }
113
+ }
@@ -0,0 +1,355 @@
1
+ /**
2
+ * ___ ___ __
3
+ * \ \/ / / _
4
+ * \ / /_| |_
5
+ * / \____ _|
6
+ * /__/\__\ |_|
7
+ *
8
+ * @file knob.ts
9
+ * @author Etienne Cochard
10
+ *
11
+ * @copyright (c) 2026 R-libre ingenierie
12
+ *
13
+ * Use of this source code is governed by an MIT-style license
14
+ * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
+ **/
16
+
17
+ import { EventCallback } from '../../core/core_events';
18
+ import { Component, ComponentEvents, ComponentProps, EvChange } from '../../core/component';
19
+ import { clamp, IComponentInterface, IFormElement, UnsafeHtml } from '../../core/core_tools';
20
+
21
+ import { Label } from '../label/label';
22
+
23
+ import './knob.module.scss';
24
+
25
+ export interface KnobEvents extends ComponentEvents {
26
+ change: EvChange;
27
+ }
28
+
29
+ export interface KnobProps extends ComponentProps {
30
+ value?: number;
31
+ /** range: the value between min and max (0 to 100 by default), on an arc of 270° */
32
+ min?: number;
33
+ max?: number;
34
+ step?: number;
35
+ /** the knob turns without end: the value has no min nor max */
36
+ infinite?: boolean;
37
+ /** infinite: the value of a whole turn (360 by default: the value is an angle) */
38
+ turn?: number;
39
+ /** the diameter of the dial, in px (48 by default): width and height size the whole, label included */
40
+ size?: number;
41
+ /** under the dial, with its icon */
42
+ label?: string | UnsafeHtml;
43
+ icon?: string;
44
+ /** for a Form: it reads the value as a number */
45
+ name?: string;
46
+ change?: EventCallback<EvChange>;
47
+ }
48
+
49
+ // range: the arc goes from -135° to +135°, 0 at the top
50
+ const SWEEP = 270;
51
+
52
+ /**
53
+ * a rotary button: on a range (min to max), or infinite.
54
+ * the pointer turns it around its center, the wheel (when it has the focus) and the arrows by step.
55
+ * infinite: a double click sets it back to its first value.
56
+ *
57
+ * @example
58
+ * new Knob( { label: "Volume", value: 25, change: ( ev ) => player.setVolume( ev.value ) } )
59
+ * new Knob( { infinite: true, turn: 20, change: ( ev ) => list.scrollTo( ev.value ) } )
60
+ *
61
+ * @cssvar
62
+ * ```
63
+ * --knob-track
64
+ * --knob-value
65
+ * --knob-cap-background
66
+ * --knob-cap-border
67
+ * --knob-pointer
68
+ * --knob-border-focus
69
+ * --knob-track-disabled
70
+ * --knob-value-disabled
71
+ * --knob-cap-background-disabled
72
+ * ```
73
+ */
74
+
75
+ export class Knob extends Component<KnobProps,KnobEvents> {
76
+ static classPrefix = "x4";
77
+
78
+ private _value = 0;
79
+ private _raw = 0; // while dragged: the value not yet on the step
80
+ private _angle = 0; // while dragged: the angle of the pointer at the last move
81
+ private _center: { x: number, y: number } = null;
82
+
83
+ private _dial: Component = null;
84
+ private _label: Label = null;
85
+
86
+ constructor( props: KnobProps ) {
87
+ super( props );
88
+
89
+ this.mapPropEvents( props, "change" );
90
+
91
+ if( props.infinite ) {
92
+ this.addClass( 'infinite' );
93
+ }
94
+
95
+ if( props.size ) {
96
+ this.setStyleVariable( "--knob-size", props.size+"px" );
97
+ }
98
+
99
+ this.setContent( [
100
+ this._dial = new Component( { cls: "dial", content: [
101
+ new Component( { cls: "ring" } ),
102
+ new Component( { cls: "cap", content: new Component( { cls: "pointer" } ) } ),
103
+ ] } ),
104
+ this._label = new Label( { text: props.label, icon: props.icon, align: "center" } ),
105
+ ] );
106
+
107
+ this._showLabel( );
108
+
109
+ this.setAttribute( "tabindex", 0 );
110
+ this.setAttribute( "role", "slider" );
111
+
112
+ if( props.name ) {
113
+ this.setAttribute( "name", props.name );
114
+ }
115
+
116
+ // the dial is turned; the label is not
117
+ this._dial.setDOMEvents( {
118
+ pointerdown: ( ev ) => this._on_pointerdown( ev ),
119
+ pointermove: ( ev ) => this._on_pointermove( ev ),
120
+ pointerup: ( ev ) => this._on_pointerup( ev ),
121
+ pointercancel: ( ev ) => this._on_pointerup( ev ),
122
+ dblclick: ( ) => this._on_dblclick( ),
123
+ } );
124
+
125
+ this.setDOMEvents( {
126
+ wheel: ( ev ) => this._on_wheel( ev ),
127
+ keydown: ( ev ) => this._on_key( ev ),
128
+ } );
129
+
130
+ this._value = this._fix( props.value ?? this._min( ) );
131
+ this._update( );
132
+ }
133
+
134
+ private _min( ) { return this.props.min ?? 0; }
135
+ private _max( ) { return this.props.max ?? 100; }
136
+ private _step( ) { return this.props.step ?? 1; }
137
+ private _turn( ) { return this.props.turn ?? 360; }
138
+
139
+ // on the step (from min on a range), between min and max on a range
140
+ private _fix( v: number ) {
141
+ const step = this._step( );
142
+
143
+ if( this.props.infinite ) {
144
+ return step>0 ? Math.round( v/step )*step : v;
145
+ }
146
+
147
+ const min = this._min( );
148
+ const max = this._max( );
149
+
150
+ if( step>0 ) {
151
+ v = min + Math.round( (v-min)/step )*step;
152
+ }
153
+
154
+ return clamp( v, min, Math.max( min, max ) );
155
+ }
156
+
157
+ // the angle of the point of the page, around the center: 0 at the top, clockwise
158
+ private _angleOf( ev: PointerEvent ) {
159
+ const dx = ev.clientX - this._center.x;
160
+ const dy = ev.clientY - this._center.y;
161
+ return Math.atan2( dx, -dy ) * 180 / Math.PI;
162
+ }
163
+
164
+ private _on_pointerdown( ev: PointerEvent ) {
165
+ ev.stopPropagation( );
166
+ ev.preventDefault( );
167
+
168
+ this.focus( );
169
+
170
+ const rc = this._dial.getBoundingRect( );
171
+ this._center = { x: rc.left + rc.width/2, y: rc.top + rc.height/2 };
172
+ this._angle = this._angleOf( ev );
173
+ this._raw = this._value;
174
+
175
+ this.addClass( "dragging" );
176
+ this._dial.setCapture( ev.pointerId );
177
+ }
178
+
179
+ // the knob follows the turn of the pointer, not its position: no jump when it is taken,
180
+ // nor from max to min on a range
181
+ private _on_pointermove( ev: PointerEvent ) {
182
+ if( !this._center ) {
183
+ return;
184
+ }
185
+
186
+ const angle = this._angleOf( ev );
187
+ let delta = angle - this._angle;
188
+
189
+ // across ±180°: the short way
190
+ if( delta>180 ) delta -= 360;
191
+ else if( delta<-180 ) delta += 360;
192
+
193
+ this._angle = angle;
194
+
195
+ if( this.props.infinite ) {
196
+ this._raw += delta / 360 * this._turn( );
197
+ }
198
+ else {
199
+ const min = this._min( );
200
+ const max = this._max( );
201
+ this._raw = clamp( this._raw + delta / SWEEP * (max-min), min, max );
202
+ }
203
+
204
+ this._set( this._fix( this._raw ), true );
205
+ }
206
+
207
+ private _on_pointerup( ev: PointerEvent ) {
208
+ if( this._center ) {
209
+ this._dial.releaseCapture( ev.pointerId );
210
+ this.removeClass( "dragging" );
211
+ this._center = null;
212
+ }
213
+ }
214
+
215
+ // infinite: back to the value it started with (0 by default)
216
+ private _on_dblclick( ) {
217
+ if( this.props.infinite ) {
218
+ this._set( this._fix( this.props.value ?? 0 ), true );
219
+ }
220
+ }
221
+
222
+ // only with the focus: the wheel over the page scrolls it, and does not turn the knobs it meets
223
+ private _on_wheel( ev: WheelEvent ) {
224
+ if( !this.hasFocus( ) || !ev.deltaY ) {
225
+ return;
226
+ }
227
+
228
+ ev.preventDefault( );
229
+ this._inc( ev.deltaY<0 ? 1 : -1, ev.ctrlKey );
230
+ }
231
+
232
+ private _on_key( ev: KeyboardEvent ) {
233
+ switch( ev.key ) {
234
+ case "ArrowRight":
235
+ case "ArrowUp": this._inc( 1, ev.ctrlKey ); break;
236
+
237
+ case "ArrowLeft":
238
+ case "ArrowDown": this._inc( -1, ev.ctrlKey ); break;
239
+
240
+ case "PageUp": this._inc( 1, true ); break;
241
+ case "PageDown": this._inc( -1, true ); break;
242
+
243
+ case "Home": {
244
+ if( this.props.infinite ) return;
245
+ this._set( this._min( ), true );
246
+ break;
247
+ }
248
+
249
+ case "End": {
250
+ if( this.props.infinite ) return;
251
+ this._set( this._fix( this._max( ) ), true );
252
+ break;
253
+ }
254
+
255
+ default:
256
+ return;
257
+ }
258
+
259
+ ev.preventDefault( );
260
+ }
261
+
262
+ // a step, ten with ctrl
263
+ private _inc( sens: number, big: boolean ) {
264
+ const step = this._step( ) || 1;
265
+ this._set( this._fix( this._value + sens * step * (big ? 10 : 1) ), true );
266
+ }
267
+
268
+ // notify: a change made by the user (pointer, wheel, keys); not by the code, nor at the construction
269
+ private _set( v: number, notify: boolean ) {
270
+ if( v===this._value ) {
271
+ return;
272
+ }
273
+
274
+ this._value = v;
275
+ this._update( );
276
+
277
+ if( notify ) {
278
+ this.fire( "change", { value: v } );
279
+ }
280
+ }
281
+
282
+ private _update( ) {
283
+ let angle: number;
284
+
285
+ if( this.props.infinite ) {
286
+ angle = ( this._value / this._turn( ) * 360 ) % 360;
287
+ this.setAria( "aria-valuenow", this._value );
288
+ }
289
+ else {
290
+ const min = this._min( );
291
+ const max = this._max( );
292
+ const perc = max>min ? (this._value-min) / (max-min) : 0;
293
+
294
+ angle = -SWEEP/2 + perc * SWEEP;
295
+ this.setStyleVariable( "--knob-perc", perc*SWEEP + "deg" );
296
+
297
+ this.setAria( "aria-valuemin", min );
298
+ this.setAria( "aria-valuemax", max );
299
+ this.setAria( "aria-valuenow", this._value );
300
+ }
301
+
302
+ this.setStyleVariable( "--knob-angle", angle + "deg" );
303
+ }
304
+
305
+ // no label nor icon: no room under the dial
306
+ private _showLabel( ) {
307
+ this._label.show( !!( this.props.label || this.props.icon ) );
308
+ }
309
+
310
+ setLabel( text: string | UnsafeHtml ) {
311
+ this.props.label = text;
312
+ this._label.setText( text );
313
+ this._showLabel( );
314
+ }
315
+
316
+ setIcon( icon: string ) {
317
+ this.props.icon = icon;
318
+ this._label.setIcon( icon );
319
+ this._showLabel( );
320
+ }
321
+
322
+ setMin( min: number ) {
323
+ this.props.min = min;
324
+ this._value = this._fix( this._value );
325
+ this._update( );
326
+ }
327
+
328
+ setMax( max: number ) {
329
+ this.props.max = max;
330
+ this._value = this._fix( this._value );
331
+ this._update( );
332
+ }
333
+
334
+ setValue( v: number ) {
335
+ this._set( this._fix( v ), false );
336
+ }
337
+
338
+ getValue( ) {
339
+ return this._value;
340
+ }
341
+
342
+ override queryInterface<T extends IComponentInterface>( name: string ): T {
343
+ if( name=="form-element" ) {
344
+ const i: IFormElement = {
345
+ getRawValue: ( ) => this.getValue( ),
346
+ setRawValue: ( v: any ) => { this.setValue( +v ); },
347
+ isValid: ( ) => true,
348
+ };
349
+
350
+ return i as unknown as T;
351
+ }
352
+
353
+ return super.queryInterface( name );
354
+ }
355
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * ___ ___ __
3
+ * \ \/ / / _
4
+ * \ / /_| |_
5
+ * / \____ _|
6
+ * /__/\__\ |_|
7
+ *
8
+ * @file mapview.module.scss
9
+ * @author Etienne Cochard
10
+ *
11
+ * @copyright (c) 2026 R-libre ingenierie
12
+ *
13
+ * Use of this source code is governed by an MIT-style license
14
+ * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
+ **/
16
+
17
+ :root {
18
+ --mapview-background: var( --background-ternary );
19
+ --mapview-marker: var( --accent-background );
20
+ --mapview-tile-filter: none; // the dark theme darkens the tiles (themes.scss)
21
+ --mapview-credit-background: color-mix( in srgb, var( --background-primary ) 75%, transparent );
22
+ --mapview-shape-opacity: 0.2;
23
+ --mapview-shape-opacity-hover: 0.35;
24
+ }
25
+
26
+ .x4mapview {
27
+ position: relative;
28
+ overflow: hidden;
29
+ width: 400px;
30
+ height: 300px;
31
+ background-color: var( --mapview-background );
32
+ outline: none;
33
+ touch-action: none;
34
+ cursor: grab;
35
+
36
+ &.dragging {
37
+ cursor: grabbing;
38
+ }
39
+
40
+ &:focus-visible {
41
+ outline: 1px solid var( --border-focus );
42
+ outline-offset: -1px;
43
+ }
44
+
45
+ .tiles,
46
+ .markers {
47
+ position: absolute;
48
+ inset: 0;
49
+ }
50
+
51
+ // the front layer over the back one (the zoom before, while it grows or shrinks)
52
+ .tiles.front {
53
+ z-index: 1;
54
+ }
55
+
56
+ .tiles.back {
57
+ transition: transform 0.25s ease-out;
58
+ pointer-events: none;
59
+ }
60
+
61
+ // the zones: only their shapes take the pointer, the map is moved through the rest
62
+ .shapes {
63
+ position: absolute;
64
+ inset: 0;
65
+ width: 100%;
66
+ height: 100%;
67
+ z-index: 2;
68
+ pointer-events: none;
69
+ overflow: hidden;
70
+
71
+ // the palette of Chart, or the color of the zone
72
+ @for $i from 1 through 8 {
73
+ .c#{$i} {
74
+ --shape-c: var( --chart-color-#{$i} );
75
+ }
76
+ }
77
+
78
+ .shape {
79
+ fill: var( --shape-c );
80
+ fill-opacity: var( --mapview-shape-opacity );
81
+ stroke: var( --shape-c );
82
+ stroke-width: 2px;
83
+ stroke-linejoin: round;
84
+ pointer-events: visiblePainted;
85
+ cursor: pointer;
86
+ transition: fill-opacity 0.15s;
87
+
88
+ &:hover {
89
+ fill-opacity: var( --mapview-shape-opacity-hover );
90
+ }
91
+ }
92
+
93
+ .line {
94
+ fill: none;
95
+ stroke-width: 4px;
96
+ stroke-linecap: round;
97
+ }
98
+ }
99
+
100
+ .markers {
101
+ z-index: 3;
102
+ pointer-events: none;
103
+ }
104
+
105
+ // two fingers: the markers and the zones would not follow the tiles
106
+ &.pinching {
107
+ .markers,
108
+ .shapes {
109
+ visibility: hidden;
110
+ }
111
+ }
112
+
113
+ .tile {
114
+ position: absolute;
115
+ width: 256px;
116
+ height: 256px;
117
+ max-width: none;
118
+ opacity: 0;
119
+ transition: opacity 0.2s;
120
+ filter: var( --mapview-tile-filter );
121
+
122
+ &.loaded {
123
+ opacity: 1;
124
+ }
125
+ }
126
+
127
+ // the point of the pin on the place
128
+ .marker {
129
+ position: absolute;
130
+ width: 24px;
131
+ height: 32px;
132
+ transform: translate( -50%, -100% );
133
+ color: var( --mapview-marker );
134
+ filter: drop-shadow( 0 1px 2px rgba( 0, 0, 0, 0.4 ) );
135
+ pointer-events: auto;
136
+ cursor: pointer;
137
+
138
+ svg {
139
+ width: 100%;
140
+ height: 100%;
141
+ }
142
+ }
143
+
144
+ .zoom {
145
+ position: absolute;
146
+ z-index: 4;
147
+ top: 8px;
148
+ left: 8px;
149
+ gap: 2px;
150
+ cursor: default;
151
+
152
+ .x4button {
153
+ min-width: 28px;
154
+ width: 28px;
155
+ height: 28px;
156
+ padding: 0;
157
+ justify-content: center;
158
+ font-size: 16px;
159
+ }
160
+ }
161
+
162
+ .credit {
163
+ position: absolute;
164
+ z-index: 4;
165
+ right: 0;
166
+ bottom: 0;
167
+ padding: 1px 6px;
168
+ font-size: 11px;
169
+ background-color: var( --mapview-credit-background );
170
+ cursor: default;
171
+ user-select: text;
172
+
173
+ a {
174
+ color: inherit;
175
+ }
176
+ }
177
+ }