lupine.components 1.1.56 → 1.1.58

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lupine.components",
3
- "version": "1.1.56",
3
+ "version": "1.1.58",
4
4
  "license": "MIT",
5
5
  "author": "uuware.com",
6
6
  "homepage": "https://github.com/uuware/lupine.js",
@@ -3,6 +3,7 @@ import { bindGlobalStyle, getGlobalStylesId, CssProps, RefProps } from 'lupine.w
3
3
  export type RangeHookProps = {
4
4
  setValue: (val: number | [number, number]) => void;
5
5
  getValue: () => number | [number, number];
6
+ setDisabled?: (disabled: boolean) => void;
6
7
  };
7
8
 
8
9
  export type RangeProps = {
@@ -24,6 +25,7 @@ export type RangeProps = {
24
25
  trackColor?: string;
25
26
  fillColor?: string;
26
27
  showButtons?: boolean;
28
+ thumbSize?: number | string;
27
29
  };
28
30
 
29
31
  // 1. Define Static CSS completely decoupled from Reactivity (isVertical)
@@ -63,11 +65,11 @@ const rangeCss: CssProps = {
63
65
  },
64
66
  '&.horizontal .&-wrapper': {
65
67
  width: '100%',
66
- height: '20px',
68
+ height: 'var(--range-thumb-size, 20px)',
67
69
  flex: 1,
68
70
  },
69
71
  '&.vertical .&-wrapper': {
70
- width: '20px',
72
+ width: 'var(--range-thumb-size, 20px)',
71
73
  height: '100%',
72
74
  flex: 1,
73
75
  },
@@ -123,7 +125,7 @@ const rangeCss: CssProps = {
123
125
  transform: 'none',
124
126
  },
125
127
  '&.vertical .&-input': {
126
- height: '20px',
128
+ height: 'var(--range-thumb-size, 20px)',
127
129
  top: '100%',
128
130
  left: 0,
129
131
  transformOrigin: '0 0',
@@ -134,8 +136,8 @@ const rangeCss: CssProps = {
134
136
  appearance: 'none',
135
137
  WebkitAppearance: 'none',
136
138
  pointerEvents: 'auto',
137
- width: '20px',
138
- height: '20px',
139
+ width: 'var(--range-thumb-size, 20px)',
140
+ height: 'var(--range-thumb-size, 20px)',
139
141
  backgroundColor: '#fff',
140
142
  border: '2px solid var(--range-fill-color, var(--primary-accent-color, #0a74c9))',
141
143
  borderRadius: '50%',
@@ -151,8 +153,8 @@ const rangeCss: CssProps = {
151
153
  },
152
154
  '.&-input::-moz-range-thumb': {
153
155
  pointerEvents: 'auto',
154
- width: '20px',
155
- height: '20px',
156
+ width: 'var(--range-thumb-size, 20px)',
157
+ height: 'var(--range-thumb-size, 20px)',
156
158
  backgroundColor: '#fff',
157
159
  border: '2px solid var(--range-fill-color, var(--primary-accent-color, #0a74c9))',
158
160
  borderRadius: '50%',
@@ -280,6 +282,9 @@ export const Range = (props: RangeProps) => {
280
282
  const customStyle: any = { ...props.style };
281
283
  if (props.trackColor) customStyle['--range-track-color'] = props.trackColor;
282
284
  if (props.fillColor) customStyle['--range-fill-color'] = props.fillColor;
285
+ if (props.thumbSize) {
286
+ customStyle['--range-thumb-size'] = typeof props.thumbSize === 'number' ? `${props.thumbSize}px` : props.thumbSize;
287
+ }
283
288
 
284
289
  // Evaluate the immutable hash exact ONCE for all range implementations.
285
290
  const globalCssId = getGlobalStylesId(rangeCss);
@@ -307,9 +312,10 @@ export const Range = (props: RangeProps) => {
307
312
 
308
313
  let currentVal1 = val1;
309
314
  let currentVal2 = val2;
315
+ let isDisabled = !!props.disabled;
310
316
 
311
317
  const adjustByStep = (direction: -1 | 1) => {
312
- if (props.disabled) return;
318
+ if (isDisabled) return;
313
319
  if (isRangeMode) {
314
320
  let newVal = currentVal1 + direction * step;
315
321
  newVal = Math.max(min, Math.min(currentVal2, newVal));
@@ -417,7 +423,7 @@ export const Range = (props: RangeProps) => {
417
423
  };
418
424
 
419
425
  const handleContainerClick = (e: any) => {
420
- if (props.disabled) return;
426
+ if (isDisabled) return;
421
427
  if (e.target && e.target.tagName === 'INPUT') return;
422
428
 
423
429
  const wrapper = ref.$('.&-wrapper') as HTMLElement;
@@ -461,6 +467,7 @@ export const Range = (props: RangeProps) => {
461
467
  };
462
468
 
463
469
  const handleInput = (e: Event, isThumb2: boolean) => {
470
+ if (isDisabled) return;
464
471
  let val = parseFloat((e.target as HTMLInputElement).value);
465
472
 
466
473
  // Magnetic snapping
@@ -496,6 +503,7 @@ export const Range = (props: RangeProps) => {
496
503
  };
497
504
 
498
505
  const handleChange = () => {
506
+ if (isDisabled) return;
499
507
  if (props.onChange) {
500
508
  if (isRangeMode) {
501
509
  props.onChange([currentVal1, currentVal2]);
@@ -522,6 +530,21 @@ export const Range = (props: RangeProps) => {
522
530
  updateVisuals();
523
531
  };
524
532
  props.hook.getValue = () => (isRangeMode ? [currentVal1, currentVal2] : currentVal1);
533
+ props.hook.setDisabled = (disabled: boolean) => {
534
+ isDisabled = disabled;
535
+ const container = (ref.current as HTMLElement) || (ref.$?.('.&-container') as HTMLElement);
536
+ if (container) {
537
+ if (disabled) {
538
+ container.classList.add('disabled');
539
+ } else {
540
+ container.classList.remove('disabled');
541
+ }
542
+ }
543
+ const inputs = ref.$all?.('.&-input') as NodeListOf<HTMLInputElement>;
544
+ inputs?.forEach?.((input) => {
545
+ input.disabled = disabled;
546
+ });
547
+ };
525
548
  }
526
549
 
527
550
  const ref: RefProps & { onUnload?: (el: Element) => Promise<void> | void } = {
@@ -563,7 +586,7 @@ export const Range = (props: RangeProps) => {
563
586
 
564
587
  return (
565
588
  <div
566
- class={[globalCssId, '&-container', props.class, props.disabled ? 'disabled' : '', isVertical ? 'vertical' : 'horizontal']
589
+ class={[globalCssId, '&-container', props.class, isDisabled ? 'disabled' : '', isVertical ? 'vertical' : 'horizontal']
567
590
  .join(' ')
568
591
  .trim()}
569
592
  ref={ref}
@@ -588,7 +611,7 @@ export const Range = (props: RangeProps) => {
588
611
  value={val1}
589
612
  onInput={(e) => handleInput(e, false)}
590
613
  onChange={handleChange}
591
- disabled={props.disabled}
614
+ disabled={isDisabled}
592
615
  />
593
616
  <input
594
617
  type='range'
@@ -599,7 +622,7 @@ export const Range = (props: RangeProps) => {
599
622
  value={val2}
600
623
  onInput={(e) => handleInput(e, true)}
601
624
  onChange={handleChange}
602
- disabled={props.disabled}
625
+ disabled={isDisabled}
603
626
  />
604
627
  </>
605
628
  ) : (
@@ -612,7 +635,7 @@ export const Range = (props: RangeProps) => {
612
635
  value={val1}
613
636
  onInput={(e) => handleInput(e, false)}
614
637
  onChange={handleChange}
615
- disabled={props.disabled}
638
+ disabled={isDisabled}
616
639
  />
617
640
  )}
618
641
 
@@ -11,6 +11,7 @@ export type ActionSheetColorPickerProps = {
11
11
  /** Show the 16 preset color swatches. Default: true */
12
12
  showPresets?: boolean;
13
13
  zIndex?: string;
14
+ colorList?: string[];
14
15
  };
15
16
 
16
17
  function hexToRgb(hex: string): [number, number, number] {
@@ -55,6 +56,7 @@ export const ActionSheetColorPicker = async ({
55
56
  cancelButtonText = 'Cancel',
56
57
  showPresets = true,
57
58
  zIndex,
59
+ colorList = PRESET_COLORS,
58
60
  }: ActionSheetColorPickerProps): Promise<string | undefined> => {
59
61
  let [r, g, b] = hexToRgb(value);
60
62
 
@@ -238,7 +240,7 @@ export const ActionSheetColorPicker = async ({
238
240
  </div>
239
241
  {showPresets && (
240
242
  <div class='presets'>
241
- {PRESET_COLORS.map((hex) => (
243
+ {colorList.map((hex) => (
242
244
  <div
243
245
  class='preset-dot'
244
246
  style={{ backgroundColor: hex }}
@@ -1,8 +1,13 @@
1
- // import { NotificationColor, NotificationMessage, updateTheme, PopupMenuWithIcon, CssProps } from 'lupine.components';
1
+ // import { NotificationColor, NotificationMessage, updateTheme, PopupMenuWithIcon, CssProps } from "lupine.components";
2
2
  import { CssProps } from 'lupine.components';
3
3
  import { MobileSideMenuHelper } from './mobile-side-menu';
4
4
 
5
- export const MobileTopSysIcon = () => {
5
+ export interface MobileTopSysIconProps {
6
+ icon?: string;
7
+ }
8
+
9
+ export const MobileTopSysIcon = (props: MobileTopSysIconProps = {}) => {
10
+ const icon = props?.icon || 'bs-list';
6
11
  const css: CssProps = {
7
12
  cursor: 'pointer',
8
13
  display: 'flex',
@@ -20,7 +25,7 @@ export const MobileTopSysIcon = () => {
20
25
  };
21
26
  return (
22
27
  <div css={css} onClick={() => MobileSideMenuHelper.show()} class='mobile-top-sys-icon'>
23
- <i class='ifc-icon bs-list'></i>
28
+ <i class={`ifc-icon ${icon}`}></i>
24
29
  </div>
25
30
  );
26
31
  };
@@ -1,4 +1,5 @@
1
1
  import { CssProps, bindGlobalStyle } from 'lupine.web';
2
+ import { isSafeAreaSupported } from '../lib/support-safe-area';
2
3
  /**
3
4
  How to use:
4
5
  Notification.sendMessage(message);
@@ -56,6 +57,8 @@ export class NotificationMessage {
56
57
  padding: '0 10px',
57
58
  zIndex: 'var(--layer-notice)',
58
59
  pointerEvents: 'none',
60
+ 'padding-top ': isSafeAreaSupported() ? 'constant(safe-area-inset-top)' : '',
61
+ 'padding-top': isSafeAreaSupported() ? 'env(safe-area-inset-top)' : '',
59
62
  '>div': {
60
63
  pointerEvents: 'auto',
61
64
  color: 'var(--notice-color-with-bg)',
@@ -1,130 +1,209 @@
1
1
  import { CssProps, RefProps, VNode, bindGlobalStyle } from 'lupine.components';
2
2
 
3
+ export interface SlideTabHookProps {
4
+ moveTo?: (index: number) => void;
5
+ getIndex?: () => number;
6
+ }
7
+
8
+ export interface SlideTabPage {
9
+ id?: string;
10
+ title: string;
11
+ content: VNode<any> | any;
12
+ }
13
+
3
14
  export interface SlideTabProps {
4
- pages: { title: string; content: VNode<any> }[];
15
+ pages: SlideTabPage[];
16
+ hook?: SlideTabHookProps;
17
+ initialIndex?: number;
18
+ onChange?: (index: number, page: SlideTabPage) => void;
19
+ navClass?: string;
20
+ class?: string;
5
21
  }
22
+
6
23
  export const SlideTabComponent = (props: SlideTabProps) => {
7
24
  const css: CssProps = {
8
25
  display: 'flex',
9
26
  flexDirection: 'column',
10
27
  flex: 1,
11
- fontSize: '12px',
12
- borderRadius: '6px',
13
- padding: '0px 8px 4px 8px',
14
- // marginBottom: '8px',
28
+ width: '100%',
15
29
  height: '100%',
30
+ overflow: 'hidden',
31
+ position: 'relative',
32
+ boxSizing: 'border-box',
33
+
34
+ '.slide-tab-c-nav': {
35
+ display: 'flex',
36
+ flexDirection: 'row',
37
+ alignItems: 'center',
38
+ backgroundColor: 'var(--card-bg, #ffffff)',
39
+ borderBottom: '0.5px solid var(--card-border, rgba(60, 60, 67, 0.15))',
40
+ padding: '10px 16px 8px 16px',
41
+ overflowX: 'auto',
42
+ WebkitOverflowScrolling: 'touch',
43
+ flexShrink: 0,
44
+ zIndex: 10,
45
+ scrollbarWidth: 'none',
46
+ '&::-webkit-scrollbar': { display: 'none' },
47
+ },
48
+ '.slide-tab-c-nav-wrap': {
49
+ display: 'flex',
50
+ flexDirection: 'row',
51
+ alignItems: 'center',
52
+ gap: '8px',
53
+ flexShrink: 0,
54
+ paddingRight: '16px',
55
+ },
56
+ '.slide-tab-c-nav-item': {
57
+ cursor: 'pointer',
58
+ padding: '7px 16px',
59
+ borderRadius: '20px',
60
+ fontSize: '13px',
61
+ fontWeight: 600,
62
+ color: 'var(--text-secondary, #8e8e93)',
63
+ backgroundColor: 'var(--bg-container, #f2f2f7)',
64
+ whiteSpace: 'nowrap',
65
+ transition: 'all 0.18s ease',
66
+ WebkitTapHighlightColor: 'transparent',
67
+ flexShrink: 0,
68
+ userSelect: 'none',
69
+ WebkitUserSelect: 'none',
70
+ '&:active': {
71
+ transform: 'scale(0.96)',
72
+ opacity: 0.85,
73
+ },
74
+ },
75
+ '.slide-tab-c-nav-item.active': {
76
+ backgroundColor: 'var(--primary-accent-color, #0284c7)',
77
+ color: '#ffffff',
78
+ boxShadow: '0 2px 8px rgba(2, 132, 199, 0.35)',
79
+ },
80
+
16
81
  '.slide-tab-c-list': {
17
82
  flex: 1,
18
- borderRadius: '6px',
19
83
  display: 'flex',
84
+ flexDirection: 'row',
20
85
  overflowX: 'auto',
86
+ overflowY: 'hidden',
21
87
  width: '100%',
88
+ height: '100%',
22
89
  scrollSnapType: 'x mandatory',
23
- gap: '8px',
24
- paddingBottom: '10px',
25
90
  scrollBehavior: 'smooth',
26
91
  WebkitOverflowScrolling: 'touch',
92
+ boxSizing: 'border-box',
93
+ scrollbarWidth: 'none',
94
+ '&::-webkit-scrollbar': { display: 'none' },
27
95
  },
28
96
  '.slide-tab-c-slide': {
29
97
  width: '100%',
30
- overflow: 'hidden',
31
- position: 'relative',
32
98
  minWidth: '100%',
99
+ maxWidth: '100%',
33
100
  flexShrink: 0,
34
- scrollSnapAlign: 'start',
35
101
  height: '100%',
36
102
  overflowY: 'auto',
37
- },
38
-
39
- '.slide-tab-c-nav': {
40
- display: 'flex',
41
- flexDirection: 'row',
42
- justifyContent: 'center',
43
- backgroundColor: 'var(--primary-bg-color)',
44
- position: 'sticky',
45
- top: 0,
46
- zIndex: 1,
47
- },
48
- '.slide-tab-c-nav-wrap': {
49
- display: 'flex',
50
- flexDirection: 'row',
51
- justifyContent: 'center',
52
- padding: '2px 4px',
53
- borderRadius: '4px',
54
- backgroundColor: 'var(--secondary-bg-color)',
55
- },
56
- '.slide-tab-c-nav-item': {
57
- cursor: 'pointer',
58
- padding: '4px 8px',
59
- borderRadius: '4px',
60
- marginRight: '8px',
61
- },
62
- '.slide-tab-c-nav-item.active': {
63
- backgroundColor: 'var(--primary-accent-color)',
64
- color: 'white',
103
+ position: 'relative',
104
+ scrollSnapAlign: 'start',
105
+ scrollSnapStop: 'always',
106
+ WebkitOverflowScrolling: 'touch',
107
+ boxSizing: 'border-box',
108
+ scrollbarWidth: 'none',
109
+ '&::-webkit-scrollbar': { display: 'none' },
65
110
  },
66
111
  };
67
112
  bindGlobalStyle('slide-tab-c-box', css);
68
113
 
69
114
  const ref: RefProps = {};
70
- let slideIndex = 0;
115
+ let slideIndex = props.initialIndex || 0;
71
116
  let manualScroll = false;
72
- let scrollEndTimer: NodeJS.Timeout | null = null;
73
- const drawerScroll = () => {
74
- if (manualScroll) {
75
- return;
76
- }
77
- if (scrollEndTimer) {
78
- clearTimeout(scrollEndTimer);
79
- }
80
- scrollEndTimer = setTimeout(() => {
81
- drawerScrollStop();
82
- }, 100);
83
- };
117
+ let scrollEndTimer: any = null;
118
+
84
119
  const resetSlides = (index: number) => {
85
- const dots = ref.$all('.slide-tab-c-nav-item');
86
- for (let i = 0; i < dots.length; i++) {
87
- dots[i].classList.toggle('active', i === index);
120
+ const dots = ref.$all?.('.slide-tab-c-nav-item');
121
+ if (dots) {
122
+ for (let i = 0; i < dots.length; i++) {
123
+ dots[i].classList.toggle('active', i === index);
124
+ }
125
+ dots[index]?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
126
+ }
127
+ if (props.onChange && props.pages[index]) {
128
+ props.onChange(index, props.pages[index]);
88
129
  }
89
130
  };
131
+
90
132
  const drawerScrollStop = () => {
91
- const drawer = ref.$('.slide-tab-c-list');
133
+ const drawer = ref.$?.('.slide-tab-c-list') as HTMLElement | undefined;
134
+ if (!drawer) return;
92
135
  const width = drawer.clientWidth;
136
+ if (width <= 0) return;
93
137
  const currentScrollIndex = Math.round(drawer.scrollLeft / width);
94
- slideIndex = currentScrollIndex;
95
- resetSlides(slideIndex);
96
- };
97
- const moveSlide = (slideIndex: number) => {
98
- const drawer = ref.$('.slide-tab-c-list');
99
- const children = ref.$all('.slide-tab-c-slide');
100
- if (!drawer || !children || children.length === 0) {
101
- return;
102
- }
103
- const target = children[slideIndex];
104
- if (!target) {
105
- return;
138
+ if (currentScrollIndex !== slideIndex && currentScrollIndex >= 0 && currentScrollIndex < props.pages.length) {
139
+ slideIndex = currentScrollIndex;
140
+ resetSlides(slideIndex);
106
141
  }
107
- const offsetLeft = target.offsetLeft;
142
+ };
143
+
144
+ const drawerScroll = () => {
145
+ if (manualScroll) return;
146
+ if (scrollEndTimer) clearTimeout(scrollEndTimer);
147
+ scrollEndTimer = setTimeout(() => {
148
+ drawerScrollStop();
149
+ }, 60);
150
+ };
151
+
152
+ const moveSlide = (targetIndex: number) => {
153
+ const drawer = ref.$?.('.slide-tab-c-list') as HTMLElement | undefined;
154
+ if (!drawer) return;
155
+ const width = drawer.clientWidth;
156
+ if (width <= 0) return;
157
+
108
158
  manualScroll = true;
159
+ slideIndex = targetIndex;
160
+ resetSlides(slideIndex);
161
+
109
162
  drawer.scrollTo({
110
- left: offsetLeft,
163
+ left: targetIndex * width,
111
164
  behavior: 'smooth',
112
165
  });
113
166
 
114
- resetSlides(slideIndex);
115
167
  setTimeout(() => {
116
168
  manualScroll = false;
117
- }, 300);
169
+ }, 350);
170
+ };
171
+
172
+ if (props.hook) {
173
+ props.hook.moveTo = moveSlide;
174
+ props.hook.getIndex = () => slideIndex;
175
+ }
176
+
177
+ ref.onLoad = async () => {
178
+ const drawer = ref.$?.('.slide-tab-c-list') as HTMLElement | undefined;
179
+ if (drawer) {
180
+ drawer.addEventListener('scrollend', drawerScrollStop);
181
+ }
182
+ if (slideIndex > 0) {
183
+ setTimeout(() => {
184
+ moveSlide(slideIndex);
185
+ }, 50);
186
+ }
187
+ };
188
+
189
+ ref.onUnload = async () => {
190
+ const drawer = ref.$?.('.slide-tab-c-list') as HTMLElement | undefined;
191
+ if (drawer) {
192
+ drawer.removeEventListener('scrollend', drawerScrollStop);
193
+ }
194
+ if (scrollEndTimer) {
195
+ clearTimeout(scrollEndTimer);
196
+ }
118
197
  };
119
198
 
120
199
  return (
121
- <section class='slide-tab-c-box' ref={ref}>
122
- <div class='slide-tab-c-nav'>
123
- <div class='slide-tab-c-nav-wrap'>
200
+ <section class={`slide-tab-c-box ${props.class || ''}`} ref={ref}>
201
+ <div class={`slide-tab-c-nav no-scrollbar-container ${props.navClass || ''}`}>
202
+ <div class="slide-tab-c-nav-wrap">
124
203
  {props.pages.map((page, index) => (
125
204
  <div
126
- class={`slide-tab-c-nav-item ${index === 0 ? 'active' : ''}`}
127
- onClick={(event) => {
205
+ class={`slide-tab-c-nav-item ${index === slideIndex ? 'active' : ''}`}
206
+ onClick={(event: any) => {
128
207
  event.preventDefault();
129
208
  moveSlide(index);
130
209
  }}
@@ -134,9 +213,9 @@ export const SlideTabComponent = (props: SlideTabProps) => {
134
213
  ))}
135
214
  </div>
136
215
  </div>
137
- <div class='slide-tab-c-list no-scrollbar-container' onScroll={drawerScroll}>
216
+ <div class="slide-tab-c-list no-scrollbar-container" onScroll={drawerScroll}>
138
217
  {props.pages.map((page) => (
139
- <div class='slide-tab-c-slide no-scrollbar-container'>{page.content}</div>
218
+ <div class="slide-tab-c-slide no-scrollbar-container">{page.content}</div>
140
219
  ))}
141
220
  </div>
142
221
  </section>