@jsenv/navi 0.7.3 → 0.9.0

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.
@@ -1,97 +1,19 @@
1
1
  import { withPropsStyle } from "../props_composition/with_props_style.js";
2
-
3
- export const consumeSpacingProps = (props) => {
4
- const style = {};
5
-
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;
21
-
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
- }
45
- }
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
- }
86
- }
87
-
88
- return style;
89
- };
2
+ import { useLayoutStyle } from "./use_layout_style.js";
90
3
 
91
4
  export const Spacing = ({ style, children, ...rest }) => {
92
- const styleForSpacing = consumeSpacingProps(rest);
5
+ const { padding, margin } = useLayoutStyle(rest);
93
6
  return (
94
- <div {...rest} style={withPropsStyle(styleForSpacing, style)}>
7
+ <div
8
+ {...rest}
9
+ style={withPropsStyle(
10
+ {
11
+ ...margin,
12
+ ...padding,
13
+ },
14
+ style,
15
+ )}
16
+ >
95
17
  {children}
96
18
  </div>
97
19
  );
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Layout Style Hook
3
+ *
4
+ * This hook processes layout-related props and converts them into CSS styles.
5
+ * It handles spacing (margin/padding), alignment (alignX/alignY), and expansion behavior.
6
+ * The hook is context-aware and adapts behavior based on flex direction.
7
+ *
8
+ * Key features:
9
+ * - Spacing: margin/padding with X/Y shortcuts and directional variants
10
+ * - Alignment: alignX/alignY using align-self and auto margins
11
+ * - Expansion: expand prop for taking remaining space (flexGrow or width: 100%)
12
+ * - Context-aware: behavior changes based on FlexDirectionContext (row/column/none)
13
+ */
14
+
15
+ import { useContext } from "preact/hooks";
16
+
17
+ import { FlexDirectionContext } from "./layout_context.jsx";
18
+
19
+ /**
20
+ * Converts layout props into CSS styles
21
+ * @param {Object} props - Component props containing layout properties
22
+ * @param {string|number} [props.margin] - All-sides margin
23
+ * @param {string|number} [props.marginX] - Horizontal margin (left + right)
24
+ * @param {string|number} [props.marginY] - Vertical margin (top + bottom)
25
+ * @param {string|number} [props.marginLeft] - Left margin
26
+ * @param {string|number} [props.marginRight] - Right margin
27
+ * @param {string|number} [props.marginTop] - Top margin
28
+ * @param {string|number} [props.marginBottom] - Bottom margin
29
+ * @param {string|number} [props.padding] - All-sides padding
30
+ * @param {string|number} [props.paddingX] - Horizontal padding (left + right)
31
+ * @param {string|number} [props.paddingY] - Vertical padding (top + bottom)
32
+ * @param {string|number} [props.paddingLeft] - Left padding
33
+ * @param {string|number} [props.paddingRight] - Right padding
34
+ * @param {string|number} [props.paddingTop] - Top padding
35
+ * @param {string|number} [props.paddingBottom] - Bottom padding
36
+ * @param {"start"|"center"|"end"|"stretch"} [props.alignX] - Horizontal alignment
37
+ * @param {"start"|"center"|"end"|"stretch"} [props.alignY] - Vertical alignment
38
+ * @param {boolean} [props.expand] - Whether element should expand to fill available space
39
+ * @returns {Object} Object with categorized styles: { margin, padding, alignment, expansion, all }
40
+ */
41
+ export const useLayoutStyle = (props) => {
42
+ const flexDirection = useContext(FlexDirectionContext);
43
+
44
+ const marginStyle = {};
45
+ const paddingStyle = {};
46
+ const alignmentStyle = {};
47
+ const expansionStyle = {};
48
+
49
+ spacing: {
50
+ outer_spacing: {
51
+ const margin = props.margin;
52
+ const marginX = props.marginX;
53
+ const marginY = props.marginY;
54
+ const marginLeft = props.marginLeft;
55
+ const marginRight = props.marginRight;
56
+ const marginTop = props.marginTop;
57
+ const marginBottom = props.marginBottom;
58
+ delete props.margin;
59
+ delete props.marginX;
60
+ delete props.marginY;
61
+ delete props.marginLeft;
62
+ delete props.marginRight;
63
+ delete props.marginTop;
64
+ delete props.marginBottom;
65
+
66
+ if (margin !== undefined) {
67
+ marginStyle.margin = margin;
68
+ }
69
+ if (marginLeft !== undefined) {
70
+ marginStyle.marginLeft = marginLeft;
71
+ } else if (marginX !== undefined) {
72
+ marginStyle.marginLeft = marginX;
73
+ }
74
+ if (marginRight !== undefined) {
75
+ marginStyle.marginRight = marginRight;
76
+ } else if (marginX !== undefined) {
77
+ marginStyle.marginRight = marginX;
78
+ }
79
+ if (marginTop !== undefined) {
80
+ marginStyle.marginTop = marginTop;
81
+ } else if (marginY !== undefined) {
82
+ marginStyle.marginTop = marginY;
83
+ }
84
+ if (marginBottom !== undefined) {
85
+ marginStyle.marginBottom = marginBottom;
86
+ } else if (marginY !== undefined) {
87
+ marginStyle.marginBottom = marginY;
88
+ }
89
+ }
90
+ inner_spacing: {
91
+ const padding = props.padding;
92
+ const paddingX = props.paddingX;
93
+ const paddingY = props.paddingY;
94
+ const paddingLeft = props.paddingLeft;
95
+ const paddingRight = props.paddingRight;
96
+ const paddingTop = props.paddingTop;
97
+ const paddingBottom = props.paddingBottom;
98
+ delete props.padding;
99
+ delete props.paddingX;
100
+ delete props.paddingY;
101
+ delete props.paddingLeft;
102
+ delete props.paddingRight;
103
+ delete props.paddingTop;
104
+ delete props.paddingBottom;
105
+
106
+ if (padding !== undefined) {
107
+ paddingStyle.padding = padding;
108
+ }
109
+ if (paddingLeft !== undefined) {
110
+ paddingStyle.paddingLeft = paddingLeft;
111
+ } else if (paddingX !== undefined) {
112
+ paddingStyle.paddingLeft = paddingX;
113
+ }
114
+ if (paddingRight !== undefined) {
115
+ paddingStyle.paddingRight = paddingRight;
116
+ } else if (paddingX !== undefined) {
117
+ paddingStyle.paddingRight = paddingX;
118
+ }
119
+ if (paddingTop !== undefined) {
120
+ paddingStyle.paddingTop = paddingTop;
121
+ } else if (paddingY !== undefined) {
122
+ paddingStyle.paddingTop = paddingY;
123
+ }
124
+ if (paddingBottom !== undefined) {
125
+ paddingStyle.paddingBottom = paddingBottom;
126
+ } else if (paddingY !== undefined) {
127
+ paddingStyle.paddingBottom = paddingY;
128
+ }
129
+ }
130
+ }
131
+
132
+ align: {
133
+ const alignX = props.alignX;
134
+ const alignY = props.alignY;
135
+ delete props.alignX;
136
+ delete props.alignY;
137
+
138
+ // flex
139
+ if (flexDirection === "row") {
140
+ // In row direction: alignX controls justify-content, alignY controls align-self
141
+ if (alignY !== undefined && alignY !== "start") {
142
+ alignmentStyle.alignSelf = alignY;
143
+ }
144
+ // For row, alignX uses auto margins for positioning
145
+ // NOTE: Auto margins only work effectively for positioning individual items.
146
+ // When multiple adjacent items have the same auto margin alignment (e.g., alignX="end"),
147
+ // only the first item will be positioned as expected because subsequent items
148
+ // will be positioned relative to the previous item's margins, not the container edge.
149
+ if (alignX !== undefined) {
150
+ if (alignX === "start") {
151
+ alignmentStyle.marginRight = "auto";
152
+ } else if (alignX === "end") {
153
+ alignmentStyle.marginLeft = "auto";
154
+ } else if (alignX === "center") {
155
+ alignmentStyle.marginLeft = "auto";
156
+ alignmentStyle.marginRight = "auto";
157
+ }
158
+ }
159
+ } else if (flexDirection === "column") {
160
+ // In column direction: alignX controls align-self, alignY uses auto margins
161
+ if (alignX !== undefined && alignX !== "start") {
162
+ alignmentStyle.alignSelf = alignX;
163
+ }
164
+ // For column, alignY uses auto margins for positioning
165
+ // NOTE: Same auto margin limitation applies - multiple adjacent items with
166
+ // the same alignY won't all position relative to container edges.
167
+ if (alignY !== undefined) {
168
+ if (alignY === "start") {
169
+ alignmentStyle.marginBottom = "auto";
170
+ } else if (alignY === "end") {
171
+ alignmentStyle.marginTop = "auto";
172
+ } else if (alignY === "center") {
173
+ alignmentStyle.marginTop = "auto";
174
+ alignmentStyle.marginBottom = "auto";
175
+ }
176
+ }
177
+ }
178
+ // non flex
179
+ else {
180
+ if (alignX === "start") {
181
+ alignmentStyle.marginRight = "auto";
182
+ } else if (alignX === "center") {
183
+ alignmentStyle.marginLeft = "auto";
184
+ alignmentStyle.marginRight = "auto";
185
+ } else if (alignX === "end") {
186
+ alignmentStyle.marginLeft = "auto";
187
+ }
188
+
189
+ if (alignY === "start") {
190
+ alignmentStyle.marginBottom = "auto";
191
+ } else if (alignY === "center") {
192
+ alignmentStyle.marginTop = "auto";
193
+ alignmentStyle.marginBottom = "auto";
194
+ } else if (alignY === "end") {
195
+ alignmentStyle.marginTop = "auto";
196
+ }
197
+ }
198
+ }
199
+
200
+ expand: {
201
+ const expand = props.expand;
202
+ delete props.expand;
203
+ if (expand) {
204
+ if (flexDirection === "row") {
205
+ expansionStyle.flexGrow = 1;
206
+ } else if (flexDirection === "column") {
207
+ expansionStyle.flexGrow = 1;
208
+ } else {
209
+ expansionStyle.width = "100%";
210
+ }
211
+ }
212
+ }
213
+
214
+ // Merge all styles for convenience
215
+ const allStyles = {
216
+ ...marginStyle,
217
+ ...paddingStyle,
218
+ ...alignmentStyle,
219
+ ...expansionStyle,
220
+ };
221
+
222
+ return {
223
+ margin: marginStyle,
224
+ padding: paddingStyle,
225
+ alignment: alignmentStyle,
226
+ expansion: expansionStyle,
227
+ all: allStyles,
228
+ };
229
+ };
@@ -12,7 +12,10 @@ import { useConstraints } from "../../validation/hooks/use_constraints.js";
12
12
  import { renderActionableComponent } from "../action_execution/render_actionable_component.jsx";
13
13
  import { useRequestedActionStatus } from "../field/use_action_events.js";
14
14
  import { useKeyboardShortcuts } from "../keyboard_shortcuts/keyboard_shortcuts.js";
15
+ import { useLayoutStyle } from "../layout/use_layout_style.js";
15
16
  import { LoadableInlineElement } from "../loader/loader_background.jsx";
17
+ import { withPropsClassName } from "../props_composition/with_props_class_name.js";
18
+ import { withPropsStyle } from "../props_composition/with_props_style.js";
16
19
  import {
17
20
  SelectionContext,
18
21
  useSelectableElement,
@@ -90,7 +93,6 @@ const LinkBasic = forwardRef((props, ref) => {
90
93
  });
91
94
  const LinkPlain = forwardRef((props, ref) => {
92
95
  const {
93
- className = "",
94
96
  loading,
95
97
  readOnly,
96
98
  disabled,
@@ -103,6 +105,10 @@ const LinkPlain = forwardRef((props, ref) => {
103
105
  onClick,
104
106
  onKeyDown,
105
107
  href,
108
+
109
+ // visual
110
+ className,
111
+ style,
106
112
  ...rest
107
113
  } = props;
108
114
  const innerRef = useRef();
@@ -114,6 +120,10 @@ const LinkPlain = forwardRef((props, ref) => {
114
120
  const shouldDimColor = readOnly || disabled;
115
121
  useDimColorWhen(innerRef, shouldDimColor);
116
122
 
123
+ const innerClassName = withPropsClassName("navi_link", className);
124
+ const { all } = useLayoutStyle(rest);
125
+ const innerStyle = withPropsStyle(all, style);
126
+
117
127
  return (
118
128
  <LoadableInlineElement
119
129
  loading={loading}
@@ -123,7 +133,8 @@ const LinkPlain = forwardRef((props, ref) => {
123
133
  {...rest}
124
134
  ref={innerRef}
125
135
  href={href}
126
- className={["navi_link", ...className.split(" ")].join(" ")}
136
+ className={innerClassName}
137
+ style={innerStyle}
127
138
  aria-busy={loading}
128
139
  inert={disabled}
129
140
  data-field=""
@@ -1,4 +1,4 @@
1
- import { consumeSpacingProps } from "../layout/spacing.jsx";
1
+ import { useLayoutStyle } from "../layout/use_layout_style.js";
2
2
  import { withPropsStyle } from "../props_composition/with_props_style.js";
3
3
 
4
4
  import.meta.css = /* css */ `
@@ -31,28 +31,16 @@ export const Text = ({
31
31
  italic,
32
32
  underline,
33
33
  style,
34
- alignX,
35
34
  ...rest
36
35
  }) => {
36
+ const { all } = useLayoutStyle(rest);
37
37
  const innerStyle = withPropsStyle(
38
38
  {
39
+ ...all,
39
40
  color,
40
41
  fontWeight: bold ? "bold" : undefined,
41
42
  fontStyle: italic ? "italic" : undefined,
42
43
  textDecoration: underline ? "underline" : undefined,
43
- ...consumeSpacingProps(rest),
44
- ...(alignX === "start"
45
- ? {}
46
- : alignX === "center"
47
- ? {
48
- alignSelf: "center",
49
- marginLeft: "auto",
50
- marginRight: "auto",
51
- }
52
- : {
53
- alignSelf: "end",
54
- marginLeft: "auto",
55
- }),
56
44
  },
57
45
  style,
58
46
  );