@jsenv/navi 0.6.2 → 0.7.1
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/dist/jsenv_navi.js +320 -154
- package/index.js +5 -0
- package/package.json +2 -2
- package/src/components/action_execution/use_execute_action.js +3 -1
- package/src/components/callout/callout.js +2 -2
- package/src/components/field/button.jsx +4 -13
- package/src/components/field/form.jsx +2 -0
- package/src/components/layout/demos/demo_flex.html +506 -0
- package/src/components/layout/flex.jsx +154 -0
- package/src/components/layout/spacing.jsx +78 -67
- package/src/components/props_composition/with_props_style.js +7 -67
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { createContext } from "preact";
|
|
2
|
+
import { useContext } from "preact/hooks";
|
|
3
|
+
|
|
4
|
+
import { withPropsClassName } from "../props_composition/with_props_class_name.js";
|
|
5
|
+
import { withPropsStyle } from "../props_composition/with_props_style.js";
|
|
6
|
+
import { consumeSpacingProps } from "./spacing.jsx";
|
|
7
|
+
|
|
8
|
+
import.meta.css = /* css */ `
|
|
9
|
+
.navi_flex_row {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: row;
|
|
12
|
+
align-items: center;
|
|
13
|
+
gap: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.navi_flex_column {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
align-items: center;
|
|
20
|
+
gap: 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.navi_flex_item {
|
|
24
|
+
flex-shrink: 0;
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const FlexDirectionContext = createContext();
|
|
29
|
+
|
|
30
|
+
export const FlexRow = ({ alignY, gap, style, children, ...rest }) => {
|
|
31
|
+
const innerStyle = withPropsStyle(
|
|
32
|
+
{
|
|
33
|
+
alignItems: alignY,
|
|
34
|
+
gap,
|
|
35
|
+
...consumeSpacingProps(rest),
|
|
36
|
+
},
|
|
37
|
+
style,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div {...rest} className="navi_flex_row" style={innerStyle}>
|
|
42
|
+
<FlexDirectionContext.Provider value="row">
|
|
43
|
+
{children}
|
|
44
|
+
</FlexDirectionContext.Provider>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
export const FlexColumn = ({ alignX, gap, style, children, ...rest }) => {
|
|
49
|
+
const innerStyle = withPropsStyle(
|
|
50
|
+
{
|
|
51
|
+
alignItems: alignX,
|
|
52
|
+
gap,
|
|
53
|
+
...consumeSpacingProps(rest),
|
|
54
|
+
},
|
|
55
|
+
style,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div {...rest} className="navi_flex_column" style={innerStyle}>
|
|
60
|
+
<FlexDirectionContext.Provider value="column">
|
|
61
|
+
{children}
|
|
62
|
+
</FlexDirectionContext.Provider>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
export const useConsumAlignProps = (props) => {
|
|
67
|
+
const flexDirection = useContext(FlexDirectionContext);
|
|
68
|
+
|
|
69
|
+
const alignX = props.alignX;
|
|
70
|
+
const alignY = props.alignY;
|
|
71
|
+
delete props.alignX;
|
|
72
|
+
delete props.alignY;
|
|
73
|
+
|
|
74
|
+
const style = {};
|
|
75
|
+
|
|
76
|
+
if (flexDirection === "row") {
|
|
77
|
+
// In row direction: alignX controls justify-content, alignY controls align-self
|
|
78
|
+
// Default alignY is "center" from CSS, so only set alignSelf when different
|
|
79
|
+
if (alignY !== undefined && alignY !== "center") {
|
|
80
|
+
style.alignSelf = alignY;
|
|
81
|
+
}
|
|
82
|
+
// For row, alignX uses auto margins for positioning
|
|
83
|
+
// NOTE: Auto margins only work effectively for positioning individual items.
|
|
84
|
+
// When multiple adjacent items have the same auto margin alignment (e.g., alignX="end"),
|
|
85
|
+
// only the first item will be positioned as expected because subsequent items
|
|
86
|
+
// will be positioned relative to the previous item's margins, not the container edge.
|
|
87
|
+
if (alignX !== undefined) {
|
|
88
|
+
if (alignX === "start") {
|
|
89
|
+
style.marginRight = "auto";
|
|
90
|
+
} else if (alignX === "end") {
|
|
91
|
+
style.marginLeft = "auto";
|
|
92
|
+
} else if (alignX === "center") {
|
|
93
|
+
style.marginLeft = "auto";
|
|
94
|
+
style.marginRight = "auto";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} else if (flexDirection === "column") {
|
|
98
|
+
// In column direction: alignX controls align-self, alignY uses auto margins
|
|
99
|
+
// Default alignX is "center" from CSS, so only set alignSelf when different
|
|
100
|
+
if (alignX !== undefined && alignX !== "center") {
|
|
101
|
+
style.alignSelf = alignX;
|
|
102
|
+
}
|
|
103
|
+
// For column, alignY uses auto margins for positioning
|
|
104
|
+
// NOTE: Same auto margin limitation applies - multiple adjacent items with
|
|
105
|
+
// the same alignY won't all position relative to container edges.
|
|
106
|
+
if (alignY !== undefined) {
|
|
107
|
+
if (alignY === "start") {
|
|
108
|
+
style.marginBottom = "auto";
|
|
109
|
+
} else if (alignY === "end") {
|
|
110
|
+
style.marginTop = "auto";
|
|
111
|
+
} else if (alignY === "center") {
|
|
112
|
+
style.marginTop = "auto";
|
|
113
|
+
style.marginBottom = "auto";
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return style;
|
|
119
|
+
};
|
|
120
|
+
export const FlexItem = ({
|
|
121
|
+
alignX,
|
|
122
|
+
alignY,
|
|
123
|
+
grow,
|
|
124
|
+
shrink,
|
|
125
|
+
className,
|
|
126
|
+
style,
|
|
127
|
+
children,
|
|
128
|
+
...rest
|
|
129
|
+
}) => {
|
|
130
|
+
const flexDirection = useContext(FlexDirectionContext);
|
|
131
|
+
if (!flexDirection) {
|
|
132
|
+
console.warn(
|
|
133
|
+
"FlexItem must be used within a FlexRow or FlexColumn component.",
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const innerClassName = withPropsClassName("navi_flex_item", className);
|
|
138
|
+
const alignStyle = useConsumAlignProps({ alignX, alignY });
|
|
139
|
+
const innerStyle = withPropsStyle(
|
|
140
|
+
{
|
|
141
|
+
flexGrow: grow ? 1 : undefined,
|
|
142
|
+
flexShrink: shrink ? 1 : undefined,
|
|
143
|
+
...consumeSpacingProps(rest),
|
|
144
|
+
...alignStyle,
|
|
145
|
+
},
|
|
146
|
+
style,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<div {...rest} className={innerClassName} style={innerStyle}>
|
|
151
|
+
{children}
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
};
|
|
@@ -1,77 +1,88 @@
|
|
|
1
1
|
import { withPropsStyle } from "../props_composition/with_props_style.js";
|
|
2
2
|
|
|
3
3
|
export const consumeSpacingProps = (props) => {
|
|
4
|
-
const consume = (name) => {
|
|
5
|
-
if (Object.hasOwn(props, name)) {
|
|
6
|
-
const value = props[name];
|
|
7
|
-
delete props[name];
|
|
8
|
-
return value;
|
|
9
|
-
}
|
|
10
|
-
return undefined;
|
|
11
|
-
};
|
|
12
|
-
const margin = consume("margin");
|
|
13
|
-
const marginX = consume("marginX");
|
|
14
|
-
const marginY = consume("marginY");
|
|
15
|
-
const marginLeft = consume("marginLeft");
|
|
16
|
-
const marginRight = consume("marginRight");
|
|
17
|
-
const marginTop = consume("marginTop");
|
|
18
|
-
const marginBottom = consume("marginBottom");
|
|
19
|
-
|
|
20
4
|
const style = {};
|
|
21
|
-
if (margin !== undefined) {
|
|
22
|
-
style.margin = margin;
|
|
23
|
-
}
|
|
24
|
-
if (marginLeft !== undefined) {
|
|
25
|
-
style.marginLeft = marginLeft;
|
|
26
|
-
} else if (marginX !== undefined) {
|
|
27
|
-
style.marginLeft = marginX;
|
|
28
|
-
}
|
|
29
|
-
if (marginRight !== undefined) {
|
|
30
|
-
style.marginRight = marginRight;
|
|
31
|
-
} else if (marginX !== undefined) {
|
|
32
|
-
style.marginRight = marginX;
|
|
33
|
-
}
|
|
34
|
-
if (marginTop !== undefined) {
|
|
35
|
-
style.marginTop = marginTop;
|
|
36
|
-
} else if (marginY !== undefined) {
|
|
37
|
-
style.marginTop = marginY;
|
|
38
|
-
}
|
|
39
|
-
if (marginBottom !== undefined) {
|
|
40
|
-
style.marginBottom = marginBottom;
|
|
41
|
-
} else if (marginY !== undefined) {
|
|
42
|
-
style.marginBottom = marginY;
|
|
43
|
-
}
|
|
44
5
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
6
|
+
outer_spacing: {
|
|
7
|
+
const margin = props.margin;
|
|
8
|
+
const marginX = props.marginX;
|
|
9
|
+
const marginY = props.marginY;
|
|
10
|
+
const marginLeft = props.marginLeft;
|
|
11
|
+
const marginRight = props.marginRight;
|
|
12
|
+
const marginTop = props.marginTop;
|
|
13
|
+
const marginBottom = props.marginBottom;
|
|
14
|
+
delete props.margin;
|
|
15
|
+
delete props.marginX;
|
|
16
|
+
delete props.marginY;
|
|
17
|
+
delete props.marginLeft;
|
|
18
|
+
delete props.marginRight;
|
|
19
|
+
delete props.marginTop;
|
|
20
|
+
delete props.marginBottom;
|
|
52
21
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
22
|
+
if (margin !== undefined) {
|
|
23
|
+
style.margin = margin;
|
|
24
|
+
}
|
|
25
|
+
if (marginLeft !== undefined) {
|
|
26
|
+
style.marginLeft = marginLeft;
|
|
27
|
+
} else if (marginX !== undefined) {
|
|
28
|
+
style.marginLeft = marginX;
|
|
29
|
+
}
|
|
30
|
+
if (marginRight !== undefined) {
|
|
31
|
+
style.marginRight = marginRight;
|
|
32
|
+
} else if (marginX !== undefined) {
|
|
33
|
+
style.marginRight = marginX;
|
|
34
|
+
}
|
|
35
|
+
if (marginTop !== undefined) {
|
|
36
|
+
style.marginTop = marginTop;
|
|
37
|
+
} else if (marginY !== undefined) {
|
|
38
|
+
style.marginTop = marginY;
|
|
39
|
+
}
|
|
40
|
+
if (marginBottom !== undefined) {
|
|
41
|
+
style.marginBottom = marginBottom;
|
|
42
|
+
} else if (marginY !== undefined) {
|
|
43
|
+
style.marginBottom = marginY;
|
|
44
|
+
}
|
|
70
45
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
46
|
+
|
|
47
|
+
inner_spacing: {
|
|
48
|
+
const padding = props.padding;
|
|
49
|
+
const paddingX = props.paddingX;
|
|
50
|
+
const paddingY = props.paddingY;
|
|
51
|
+
const paddingLeft = props.paddingLeft;
|
|
52
|
+
const paddingRight = props.paddingRight;
|
|
53
|
+
const paddingTop = props.paddingTop;
|
|
54
|
+
const paddingBottom = props.paddingBottom;
|
|
55
|
+
delete props.padding;
|
|
56
|
+
delete props.paddingX;
|
|
57
|
+
delete props.paddingY;
|
|
58
|
+
delete props.paddingLeft;
|
|
59
|
+
delete props.paddingRight;
|
|
60
|
+
delete props.paddingTop;
|
|
61
|
+
delete props.paddingBottom;
|
|
62
|
+
|
|
63
|
+
if (padding !== undefined) {
|
|
64
|
+
style.padding = padding;
|
|
65
|
+
}
|
|
66
|
+
if (paddingLeft !== undefined) {
|
|
67
|
+
style.paddingLeft = paddingLeft;
|
|
68
|
+
} else if (paddingX !== undefined) {
|
|
69
|
+
style.paddingLeft = paddingX;
|
|
70
|
+
}
|
|
71
|
+
if (paddingRight !== undefined) {
|
|
72
|
+
style.paddingRight = paddingRight;
|
|
73
|
+
} else if (paddingX !== undefined) {
|
|
74
|
+
style.paddingRight = paddingX;
|
|
75
|
+
}
|
|
76
|
+
if (paddingTop !== undefined) {
|
|
77
|
+
style.paddingTop = paddingTop;
|
|
78
|
+
} else if (paddingY !== undefined) {
|
|
79
|
+
style.paddingTop = paddingY;
|
|
80
|
+
}
|
|
81
|
+
if (paddingBottom !== undefined) {
|
|
82
|
+
style.paddingBottom = paddingBottom;
|
|
83
|
+
} else if (paddingY !== undefined) {
|
|
84
|
+
style.paddingBottom = paddingY;
|
|
85
|
+
}
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
return style;
|
|
@@ -1,86 +1,26 @@
|
|
|
1
|
+
import { mergeStyles } from "@jsenv/dom";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Merges a component's base style with style received from props.
|
|
5
|
+
* Automatically normalizes style values (e.g., adds "px" units where needed).
|
|
3
6
|
*
|
|
4
7
|
* ```jsx
|
|
5
8
|
* const MyButton = ({ style, children }) => (
|
|
6
|
-
* <button style={withPropsStyle({ padding:
|
|
9
|
+
* <button style={withPropsStyle({ padding: 10 }, style)}>
|
|
7
10
|
* {children}
|
|
8
11
|
* </button>
|
|
9
12
|
* );
|
|
10
13
|
*
|
|
11
14
|
* // Usage:
|
|
12
|
-
* <MyButton style={{ color: 'red', fontSize:
|
|
15
|
+
* <MyButton style={{ color: 'red', fontSize: 14 }} />
|
|
13
16
|
* <MyButton style="color: blue; margin: 5px;" />
|
|
14
17
|
* <MyButton /> // Just base styles
|
|
15
18
|
* ```
|
|
16
19
|
*
|
|
17
20
|
* @param {string|object} baseStyle - The component's base style (string or object)
|
|
18
21
|
* @param {string|object} [styleFromProps] - Additional style from props (optional)
|
|
19
|
-
* @returns {object} The merged style object
|
|
22
|
+
* @returns {object} The merged and normalized style object
|
|
20
23
|
*/
|
|
21
24
|
export const withPropsStyle = (baseStyle, styleFromProps) => {
|
|
22
|
-
|
|
23
|
-
return baseStyle;
|
|
24
|
-
}
|
|
25
|
-
if (!baseStyle) {
|
|
26
|
-
return styleFromProps;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Parse base style to object if it's a string
|
|
30
|
-
const parsedBaseStyle =
|
|
31
|
-
typeof baseStyle === "string"
|
|
32
|
-
? parseStyleString(baseStyle)
|
|
33
|
-
: baseStyle || {};
|
|
34
|
-
// Parse props style to object if it's a string
|
|
35
|
-
const parsedPropsStyle =
|
|
36
|
-
typeof styleFromProps === "string"
|
|
37
|
-
? parseStyleString(styleFromProps)
|
|
38
|
-
: styleFromProps;
|
|
39
|
-
// Merge styles with props taking priority
|
|
40
|
-
return { ...parsedBaseStyle, ...parsedPropsStyle };
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Parses a CSS style string into a style object.
|
|
45
|
-
* Handles CSS properties with proper camelCase conversion.
|
|
46
|
-
*
|
|
47
|
-
* @param {string} styleString - CSS style string like "color: red; font-size: 14px;"
|
|
48
|
-
* @returns {object} Style object with camelCase properties
|
|
49
|
-
*/
|
|
50
|
-
const parseStyleString = (styleString) => {
|
|
51
|
-
const style = {};
|
|
52
|
-
|
|
53
|
-
if (!styleString || typeof styleString !== "string") {
|
|
54
|
-
return style;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Split by semicolon and process each declaration
|
|
58
|
-
const declarations = styleString.split(";");
|
|
59
|
-
|
|
60
|
-
for (let declaration of declarations) {
|
|
61
|
-
declaration = declaration.trim();
|
|
62
|
-
if (!declaration) continue;
|
|
63
|
-
|
|
64
|
-
const colonIndex = declaration.indexOf(":");
|
|
65
|
-
if (colonIndex === -1) continue;
|
|
66
|
-
|
|
67
|
-
const property = declaration.slice(0, colonIndex).trim();
|
|
68
|
-
const value = declaration.slice(colonIndex + 1).trim();
|
|
69
|
-
|
|
70
|
-
if (property && value) {
|
|
71
|
-
// CSS custom properties (starting with --) should NOT be converted to camelCase
|
|
72
|
-
if (property.startsWith("--")) {
|
|
73
|
-
style[property] = value;
|
|
74
|
-
} else {
|
|
75
|
-
// Convert kebab-case to camelCase (e.g., "font-size" -> "fontSize")
|
|
76
|
-
const camelCaseProperty = property.replace(
|
|
77
|
-
/-([a-z])/g,
|
|
78
|
-
(match, letter) => letter.toUpperCase(),
|
|
79
|
-
);
|
|
80
|
-
style[camelCaseProperty] = value;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return style;
|
|
25
|
+
return mergeStyles(baseStyle, styleFromProps, "css");
|
|
86
26
|
};
|