lupine.components 1.1.10 → 1.1.13

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.10",
3
+ "version": "1.1.13",
4
4
  "license": "MIT",
5
5
  "author": "uuware.com",
6
6
  "homepage": "https://github.com/uuware/lupine.js",
@@ -39,4 +39,4 @@
39
39
  "dependencies": {
40
40
  "lupine.web": "^1.0.0"
41
41
  }
42
- }
42
+ }
@@ -100,7 +100,7 @@ export class ActionSheet {
100
100
  borderRadius: '8px',
101
101
  backgroundColor: 'var(--cover-bg-color)', //'#fefefe',
102
102
  width: '100%',
103
- maxWidth: contentMaxWidth ? contentMaxWidth : `clamp(200px, 70%, 800px)`,
103
+ maxWidth: contentMaxWidth ? contentMaxWidth : `clamp(200px, 90%, 600px)`,
104
104
  margin: '0 auto',
105
105
  },
106
106
  '.act-sheet-bottom-item, .act-sheet-item': {
@@ -109,7 +109,7 @@ export class ActionSheet {
109
109
  cursor: 'pointer',
110
110
  transition: 'all 0.3s ease',
111
111
  width: '100%',
112
- maxWidth: contentMaxWidth ? contentMaxWidth : `clamp(200px, 70%, 800px)`,
112
+ maxWidth: contentMaxWidth ? contentMaxWidth : `clamp(200px, 90%, 600px)`,
113
113
  borderTop: '1px solid var(--primary-border-color)',
114
114
  },
115
115
  '.act-sheet-bottom-item': {
@@ -24,10 +24,14 @@ export * from './paging-link';
24
24
  export * from './panel';
25
25
  export * from './popup-menu';
26
26
  export * from './progress';
27
+ export * from './radio-label-component';
27
28
  export * from './redirect';
28
29
  export * from './resizable-splitter';
30
+ export * from './select-angle-component';
29
31
  export * from './select-with-title';
32
+ export * from './slide-tab-component';
30
33
  export * from './spinner';
34
+ export * from './stars-component';
31
35
  export * from './svg';
32
36
  export * from './tabs';
33
37
  export * from './text-glow';
@@ -28,9 +28,12 @@ export interface MobileHeaderTitleIconProps {
28
28
  onBack?: (event: Event) => void;
29
29
  left?: VNode<any> | HtmlVar;
30
30
  right?: VNode<any> | HtmlVar;
31
+ background?: string;
32
+ color?: string;
33
+ noShadow?: boolean;
31
34
  }
32
35
  // there may have a few MobileHeaderTitleIcon for different pages
33
- export const MobileHeaderTitleIcon = ({ title, onBack, left, right }: MobileHeaderTitleIconProps) => {
36
+ export const MobileHeaderTitleIcon = ({ title, onBack, left, right, background, color, noShadow }: MobileHeaderTitleIconProps) => {
34
37
  // const processBack = (event: Event) => {
35
38
  // if (onBack) {
36
39
  // onBack(event);
@@ -43,8 +46,11 @@ export const MobileHeaderTitleIcon = ({ title, onBack, left, right }: MobileHead
43
46
  flexDirection: 'row',
44
47
  width: '100vw',
45
48
  padding: '6px 0',
46
- backgroundColor: 'var(--activatable-bg-color-normal)',
47
- boxShadow: 'var(--mobile-header-shadow)',
49
+ // backgroundColor: 'var(--activatable-bg-color-normal)',
50
+ // boxShadow: 'var(--mobile-header-shadow)',
51
+ color: color || 'var(--primary-color)',
52
+ background: background || 'var(--activatable-bg-color-normal)',
53
+ boxShadow: noShadow ? 'unset' : 'var(--mobile-header-shadow)',
48
54
  zIndex: 'var(--layer-inside)', // bring boxShadow to front
49
55
  '.mhti-title': {
50
56
  display: 'flex',
@@ -32,6 +32,11 @@ export const HeaderWithBackFrame = ({
32
32
  left,
33
33
  right,
34
34
  noHeader = false,
35
+ background,
36
+ color,
37
+ noShadow,
38
+ contentColor,
39
+ contentBackground,
35
40
  }: {
36
41
  children: VNode<any>;
37
42
  title: VNode<any> | string | HtmlVar;
@@ -39,6 +44,11 @@ export const HeaderWithBackFrame = ({
39
44
  left?: VNode<any> | HtmlVar;
40
45
  right?: VNode<any> | HtmlVar;
41
46
  noHeader?: boolean;
47
+ color?: string;
48
+ background?: string;
49
+ noShadow?: boolean;
50
+ contentColor?: string;
51
+ contentBackground?: string;
42
52
  }) => {
43
53
  left = left || <HeaderWithBackFrameLeft onClick={onBack} />;
44
54
  right = right || <HeaderWithBackFrameRight onClick={onBack} />;
@@ -48,6 +58,7 @@ export const HeaderWithBackFrame = ({
48
58
  width: '100%',
49
59
  height: '100%',
50
60
  minHeight: '100%',
61
+ background: background || 'var(--activatable-bg-color-normal)',
51
62
  '.header-back-top': {
52
63
  display: 'flex',
53
64
  flexDirection: 'row',
@@ -63,6 +74,8 @@ export const HeaderWithBackFrame = ({
63
74
  overflowY: 'auto',
64
75
  scrollbarWidth: 'none',
65
76
  position: 'relative',
77
+ color: contentColor || 'var(--primary-color)',
78
+ background: contentBackground || 'var(--activatable-bg-color-normal)',
66
79
  '&::-webkit-scrollbar': {
67
80
  display: 'none',
68
81
  // height: '0',
@@ -97,7 +110,7 @@ export const HeaderWithBackFrame = ({
97
110
  const ref: RefProps = {};
98
111
  return (
99
112
  <div ref={ref} css={css} class='header-back-frame'>
100
- {!noHeader && <MobileHeaderTitleIcon onBack={onBack} left={domLeft} title={domCenter} right={domRight} />}
113
+ {!noHeader && <MobileHeaderTitleIcon onBack={onBack} left={domLeft} title={domCenter} right={domRight} background={background} color={color} noShadow={noShadow} />}
101
114
  <div class='header-back-content'>{children}</div>
102
115
  </div>
103
116
  );
@@ -0,0 +1,36 @@
1
+ import { bindGlobalStyle, CssProps } from 'lupine.components';
2
+
3
+ export const RadioLabelComponent = (props: {
4
+ label: string;
5
+ name: string;
6
+ checked?: boolean;
7
+ disabled?: boolean;
8
+ onChange?: (checked: boolean) => void;
9
+ className?: string;
10
+ radioClassname?: string;
11
+ }) => {
12
+ const css: CssProps = {
13
+ display: 'flex',
14
+ '& > label': {
15
+ display: 'flex',
16
+ alignItems: 'center',
17
+ },
18
+ };
19
+ bindGlobalStyle('radio-label-component', css);
20
+
21
+ return (
22
+ <div class={'radio-label-component' + (props.className ? ' ' + props.className : '')}>
23
+ <label>
24
+ <input
25
+ type='radio'
26
+ name={props.name}
27
+ class={'input-base input-s' + (props.radioClassname ? ' ' + props.radioClassname : '')}
28
+ checked={props.checked}
29
+ disabled={props.disabled}
30
+ onChange={(event) => props.onChange?.((event.target as HTMLInputElement).checked)}
31
+ />
32
+ <span class='ml-ss'>{props.label}</span>
33
+ </label>
34
+ </div>
35
+ );
36
+ };
@@ -0,0 +1,127 @@
1
+ import { CssProps, RefProps } from 'lupine.components';
2
+
3
+ export type SelectAngleComponentHookProps = {
4
+ setAngle?: (angle: number) => void;
5
+ };
6
+ export type SelectAngleComponentProps = {
7
+ size?: string;
8
+ angle: number;
9
+ onChange: (angle: number) => void;
10
+ hook?: SelectAngleComponentHookProps;
11
+ };
12
+ export const SelectAngleComponent = (props: SelectAngleComponentProps) => {
13
+ const css: CssProps = {
14
+ width: props.size || '80px',
15
+ height: props.size || '80px',
16
+ '&circle': {
17
+ width: '100%',
18
+ height: '100%',
19
+ borderRadius: '50%',
20
+ border: '2px solid #aaa',
21
+ position: 'relative',
22
+ backgroundColor: 'var(--primary-bg-color)',
23
+ cursor: 'pointer',
24
+ },
25
+ '&needle': {
26
+ width: '2px',
27
+ height: '50%',
28
+ backgroundColor: 'red',
29
+ position: 'absolute',
30
+ top: '0',
31
+ left: '50%',
32
+ transformOrigin: 'bottom center',
33
+ transform: 'rotate(90deg)',
34
+ },
35
+ '&tips': {
36
+ position: 'absolute',
37
+ top: '50%',
38
+ left: '50%',
39
+ transform: 'translate(-50%, -50%)',
40
+ fontSize: '12px',
41
+ color: 'var(--primary-color)',
42
+ fontWeight: '600',
43
+ zIndex: '10',
44
+ },
45
+ '&a0, &a90, &a180, &a270': {
46
+ width: '6px',
47
+ height: '6px',
48
+ borderRadius: '50%',
49
+ backgroundColor: '#333',
50
+ position: 'absolute',
51
+ top: '0',
52
+ left: '50%',
53
+ transform: 'translate(-50%, -50%)',
54
+ fontSize: '12px',
55
+ color: '#333',
56
+ },
57
+ '&a90': {
58
+ top: '50%',
59
+ left: '100%',
60
+ },
61
+ '&a180': {
62
+ top: '100%',
63
+ left: '50%',
64
+ },
65
+ '&a270': {
66
+ top: '50%',
67
+ left: '0',
68
+ },
69
+ };
70
+
71
+ let cx: number = 0;
72
+ let cy: number = 0;
73
+ let mv = false;
74
+ if (props.hook) {
75
+ props.hook.setAngle = (angle) => {
76
+ updateAngleSub(angle);
77
+ };
78
+ }
79
+ const updateAngle = (ev: MouseEvent) => {
80
+ const dx = ev.clientX - cx;
81
+ const dy = ev.clientY - cy;
82
+ // atan2 返回弧度,顺时针0°为右侧
83
+ let deg = Math.atan2(dy, dx) * (180 / Math.PI);
84
+ deg = (deg + 450) % 360; // 让上方为0°
85
+ updateAngleSub(deg);
86
+ };
87
+ const updateAngleSub = (deg: number) => {
88
+ const needle = ref.$('&needle');
89
+ const text = ref.$('&tips');
90
+ needle.style.transform = `rotate(${deg}deg)`;
91
+ text.textContent = `${deg.toFixed(0)}°`;
92
+ props.onChange(deg);
93
+ };
94
+
95
+ const pointerdown = (ev: MouseEvent) => {
96
+ const picker = ref.$('&circle');
97
+ const rect = picker.getBoundingClientRect();
98
+ cx = rect.left + rect.width / 2;
99
+ cy = rect.top + rect.height / 2;
100
+
101
+ updateAngle(ev);
102
+ mv = true;
103
+ };
104
+ const pointermove = (ev: MouseEvent) => {
105
+ if (!mv) {
106
+ return;
107
+ }
108
+ updateAngle(ev);
109
+ };
110
+ const pointerup = () => {
111
+ mv = false;
112
+ };
113
+ const ref: RefProps = {};
114
+ return (
115
+ <div ref={ref} css={css}>
116
+ <div class='&circle' onPointerDown={pointerdown} onPointerMove={pointermove} onPointerUp={pointerup}>
117
+ <div class='&needle'></div>
118
+ <div class='&tips'>90°</div>
119
+
120
+ <div class='&a0' onClick={() => updateAngleSub(0)}></div>
121
+ <div class='&a90' onClick={() => updateAngleSub(90)}></div>
122
+ <div class='&a180' onClick={() => updateAngleSub(180)}></div>
123
+ <div class='&a270' onClick={() => updateAngleSub(270)}></div>
124
+ </div>
125
+ </div>
126
+ );
127
+ };
@@ -0,0 +1,149 @@
1
+ import { CssProps, RefProps, VNode, bindGlobalStyle } from 'lupine.components';
2
+
3
+ export interface SlideTabProps {
4
+ pages: { title: string; content: VNode<any> }[];
5
+ };
6
+ export const SlideTabComponent = (props: SlideTabProps) => {
7
+ const css: CssProps = {
8
+ display: 'flex',
9
+ flexDirection: 'column',
10
+ flex: 1,
11
+ fontSize: '12px',
12
+ borderRadius: '6px',
13
+ padding: '0px 8px 4px 8px',
14
+ // marginBottom: '8px',
15
+ height: '100%',
16
+ '.slide-tab-c-list': {
17
+ flex: 1,
18
+ borderRadius: '6px',
19
+ display: 'flex',
20
+ overflowX: 'auto',
21
+ width: '100%',
22
+ scrollSnapType: 'x mandatory',
23
+ gap: '8px',
24
+ paddingBottom: '10px',
25
+ scrollBehavior: 'smooth',
26
+ WebkitOverflowScrolling: 'touch',
27
+ },
28
+ '.slide-tab-c-slide': {
29
+ width: '100%',
30
+ overflow: 'hidden',
31
+ position: 'relative',
32
+ minWidth: '100%',
33
+ flexShrink: 0,
34
+ scrollSnapAlign: 'start',
35
+ height: '100%',
36
+ 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',
65
+ },
66
+ };
67
+ bindGlobalStyle('slide-tab-c-box', css);
68
+
69
+ const ref: RefProps = {};
70
+ let slideIndex = 0;
71
+ 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
+ };
84
+ 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);
88
+ }
89
+ };
90
+ const drawerScrollStop = () => {
91
+ const drawer = ref.$('.slide-tab-c-list');
92
+ const width = drawer.clientWidth;
93
+ 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;
106
+ }
107
+ const offsetLeft = target.offsetLeft;
108
+ manualScroll = true;
109
+ drawer.scrollTo({
110
+ left: offsetLeft,
111
+ behavior: 'smooth',
112
+ });
113
+
114
+ resetSlides(slideIndex);
115
+ setTimeout(() => {
116
+ manualScroll = false;
117
+ }, 300);
118
+ };
119
+
120
+ 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'>
124
+ {props.pages.map((page, index) => (
125
+ <div
126
+ class={`slide-tab-c-nav-item ${index === 0 ? 'active' : ''}`}
127
+ onClick={(event) => {
128
+ event.preventDefault();
129
+ moveSlide(index);
130
+ }}
131
+ >
132
+ {page.title}
133
+ </div>
134
+ ))}
135
+
136
+ </div>
137
+ </div>
138
+ <div class='slide-tab-c-list no-scrollbar-container' onScroll={drawerScroll}>
139
+
140
+ {props.pages.map((page) => (
141
+ <div class='slide-tab-c-slide no-scrollbar-container'>
142
+ {page.content}
143
+ </div>
144
+ ))}
145
+
146
+ </div>
147
+ </section>
148
+ );
149
+ };
@@ -0,0 +1,66 @@
1
+ import { bindGlobalStyle, CssProps, RefProps } from 'lupine.components';
2
+
3
+ export type StarsHookComponentProps = {
4
+ setValue: (value: number) => void;
5
+ getValue: () => number;
6
+ };
7
+ export type StarsComponentProps = {
8
+ maxLength: number;
9
+ value: number;
10
+ onChange?: (value: number) => void;
11
+ hook?: StarsHookComponentProps;
12
+ fontSize?: string;
13
+ };
14
+ export const StarsComponent = (props: StarsComponentProps) => {
15
+ const css: CssProps = {
16
+ display: 'flex',
17
+ flexDirection: 'row',
18
+ '.stars-label': {
19
+ color: '#9d9d9d',
20
+ cursor: 'pointer',
21
+ display: 'flex',
22
+ alignItems: 'center',
23
+ },
24
+ '.stars-label.active': {
25
+ color: 'blue',
26
+ },
27
+ '.stars-label .full, .stars-label.active .outline': {
28
+ display: 'none',
29
+ },
30
+ '.stars-label.active .full, .stars-label .outline': {
31
+ display: 'inline',
32
+ },
33
+ };
34
+ bindGlobalStyle('stars-box', css);
35
+
36
+ const setValue = (value: number) => {
37
+ props.value = value;
38
+ const stars = ref.$all('.stars-label') as NodeListOf<Element>;
39
+ stars.forEach((star, index) => {
40
+ star.classList.toggle('active', index < value);
41
+ });
42
+ };
43
+ if (props.hook) {
44
+ props.hook.setValue = (value) => {
45
+ setValue(value);
46
+ };
47
+ props.hook.getValue = () => props.value;
48
+ }
49
+ const ref: RefProps = {};
50
+ return (
51
+ <div style={{ fontSize: props.fontSize || '20px' }} ref={ref} class='stars-box'>
52
+ {Array.from({ length: props.maxLength }).map((value, index) => (
53
+ <label
54
+ class={'stars-label' + (index < props.value ? ' active' : '')}
55
+ onClick={() => {
56
+ setValue(index + 1);
57
+ props.onChange?.(index + 1);
58
+ }}
59
+ >
60
+ <i class='ifc-icon ma-cards-heart full'></i>
61
+ <i class='ifc-icon ma-cards-heart-outline outline'></i>
62
+ </label>
63
+ ))}
64
+ </div>
65
+ );
66
+ };
@@ -1,7 +1,7 @@
1
- export const blobToBase64 = (blob: Blob): Promise<string> => {
1
+ export const blobToBase64 = (blob: Blob, removeMeta?: boolean): Promise<string> => {
2
2
  return new Promise((resolve, reject) => {
3
3
  const reader = new FileReader();
4
- reader.onloadend = () => resolve(reader.result as string); // data:audio/mpeg;base64,...
4
+ reader.onloadend = () => resolve(removeMeta ? (reader.result as string).split(',')[1] : reader.result as string); // data:audio/mpeg;base64,...
5
5
  reader.onerror = reject;
6
6
  reader.readAsDataURL(blob);
7
7
  });
@@ -0,0 +1,27 @@
1
+ export const encodeHtml = (str: string): string => {
2
+ return str.replace(
3
+ /[&<>'"]/g,
4
+ (tag) =>
5
+ ({
6
+ '&': '&amp;',
7
+ '<': '&lt;',
8
+ '>': '&gt;',
9
+ "'": '&#39;',
10
+ '"': '&quot;',
11
+ }[tag] || '')
12
+ );
13
+ };
14
+
15
+ export const decodeHtml = (str: string): string => {
16
+ return str.replace(
17
+ /&(\D+);/gi,
18
+ (tag) =>
19
+ ({
20
+ '&amp;': '&',
21
+ '&lt;': '<',
22
+ '&gt;': '>',
23
+ '&#39;': "'",
24
+ '&quot;': '"',
25
+ }[tag] || '')
26
+ );
27
+ };
package/src/lib/index.ts CHANGED
@@ -11,7 +11,7 @@ export * from './download-link';
11
11
  export * from './download-stream';
12
12
  export * from './drag-util';
13
13
  export * from './dynamical-load';
14
- export * from './escape-html';
14
+ export * from './encode-html';
15
15
  export * from './find-parent-tag';
16
16
  export * from './format-bytes';
17
17
  export * from './lite-dom';
@@ -1,9 +0,0 @@
1
- export const escapeHtml = (str: string | number | boolean) => {
2
- if (!str || typeof str !== 'string') return str;
3
- return str
4
- .replace(/&/g, '&amp;')
5
- .replace(/</g, '&lt;')
6
- .replace(/>/g, '&gt;')
7
- .replace(/"/g, '&quot;')
8
- .replace(/'/g, '&#39;');
9
- };