@jsenv/navi 0.6.1 → 0.7.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.
@@ -11735,7 +11735,7 @@ const openCallout = (
11735
11735
  level = "warning",
11736
11736
  onClose,
11737
11737
  closeOnClickOutside = level === "info",
11738
- hideErrorStack,
11738
+ showErrorStack,
11739
11739
  debug = false,
11740
11740
  } = {},
11741
11741
  ) => {
@@ -11803,7 +11803,7 @@ const openCallout = (
11803
11803
  if (Error.isError(newMessage)) {
11804
11804
  const error = newMessage;
11805
11805
  newMessage = error.message;
11806
- if (!hideErrorStack && error.stack) {
11806
+ if (showErrorStack && error.stack) {
11807
11807
  newMessage += `<pre class="navi_callout_error_stack">${escapeHtml(String(error.stack))}</pre>`;
11808
11808
  }
11809
11809
  }
@@ -13766,6 +13766,7 @@ const useExecuteAction = (
13766
13766
  elementRef,
13767
13767
  {
13768
13768
  errorEffect = "show_validation_message", // "show_validation_message" or "throw"
13769
+ errorMapping,
13769
13770
  } = {},
13770
13771
  ) => {
13771
13772
  // see https://medium.com/trabe/catching-asynchronous-errors-in-react-using-error-boundaries-5e8a5fd7b971
@@ -13783,7 +13784,8 @@ const useExecuteAction = (
13783
13784
  const validationMessageTargetRef = useRef(null);
13784
13785
  const addErrorMessage = (error) => {
13785
13786
  const validationMessageTarget = validationMessageTargetRef.current;
13786
- addCustomMessage(validationMessageTarget, "action_error", error, {
13787
+ const errorMapped = errorMapping ? errorMapping(error) : error;
13788
+ addCustomMessage(validationMessageTarget, "action_error", errorMapped, {
13787
13789
  // This error should not prevent <form> submission
13788
13790
  // so whenever user tries to submit the form the error is cleared
13789
13791
  // (Hitting enter key, clicking on submit button, etc. would allow to re-submit the form in error state)
@@ -18594,74 +18596,84 @@ const parseStyleString = (styleString) => {
18594
18596
  };
18595
18597
 
18596
18598
  const consumeSpacingProps = props => {
18597
- const consume = name => {
18598
- if (Object.hasOwn(props, name)) {
18599
- const value = props[name];
18600
- delete props[name];
18601
- return value;
18602
- }
18603
- return undefined;
18604
- };
18605
- const margin = consume("margin");
18606
- const marginX = consume("marginX");
18607
- const marginY = consume("marginY");
18608
- const marginLeft = consume("marginLeft");
18609
- const marginRight = consume("marginRight");
18610
- const marginTop = consume("marginTop");
18611
- const marginBottom = consume("marginBottom");
18612
18599
  const style = {};
18613
- if (margin !== undefined) {
18614
- style.margin = margin;
18615
- }
18616
- if (marginLeft !== undefined) {
18617
- style.marginLeft = marginLeft;
18618
- } else if (marginX !== undefined) {
18619
- style.marginLeft = marginX;
18620
- }
18621
- if (marginRight !== undefined) {
18622
- style.marginRight = marginRight;
18623
- } else if (marginX !== undefined) {
18624
- style.marginRight = marginX;
18625
- }
18626
- if (marginTop !== undefined) {
18627
- style.marginTop = marginTop;
18628
- } else if (marginY !== undefined) {
18629
- style.marginTop = marginY;
18630
- }
18631
- if (marginBottom !== undefined) {
18632
- style.marginBottom = marginBottom;
18633
- } else if (marginY !== undefined) {
18634
- style.marginBottom = marginY;
18635
- }
18636
- const padding = consume("padding");
18637
- const paddingX = consume("paddingX");
18638
- const paddingY = consume("paddingY");
18639
- const paddingLeft = consume("paddingLeft");
18640
- const paddingRight = consume("paddingRight");
18641
- const paddingTop = consume("paddingTop");
18642
- const paddingBottom = consume("paddingBottom");
18643
- if (padding !== undefined) {
18644
- style.padding = padding;
18645
- }
18646
- if (paddingLeft !== undefined) {
18647
- style.paddingLeft = paddingLeft;
18648
- } else if (paddingX !== undefined) {
18649
- style.paddingLeft = paddingX;
18650
- }
18651
- if (paddingRight !== undefined) {
18652
- style.paddingRight = paddingRight;
18653
- } else if (paddingX !== undefined) {
18654
- style.paddingRight = paddingX;
18655
- }
18656
- if (paddingTop !== undefined) {
18657
- style.paddingTop = paddingTop;
18658
- } else if (paddingY !== undefined) {
18659
- style.paddingTop = paddingY;
18660
- }
18661
- if (paddingBottom !== undefined) {
18662
- style.paddingBottom = paddingBottom;
18663
- } else if (paddingY !== undefined) {
18664
- style.paddingBottom = paddingY;
18600
+ {
18601
+ const margin = props.margin;
18602
+ const marginX = props.marginX;
18603
+ const marginY = props.marginY;
18604
+ const marginLeft = props.marginLeft;
18605
+ const marginRight = props.marginRight;
18606
+ const marginTop = props.marginTop;
18607
+ const marginBottom = props.marginBottom;
18608
+ delete props.margin;
18609
+ delete props.marginX;
18610
+ delete props.marginY;
18611
+ delete props.marginLeft;
18612
+ delete props.marginRight;
18613
+ delete props.marginTop;
18614
+ delete props.marginBottom;
18615
+ if (margin !== undefined) {
18616
+ style.margin = margin;
18617
+ }
18618
+ if (marginLeft !== undefined) {
18619
+ style.marginLeft = marginLeft;
18620
+ } else if (marginX !== undefined) {
18621
+ style.marginLeft = marginX;
18622
+ }
18623
+ if (marginRight !== undefined) {
18624
+ style.marginRight = marginRight;
18625
+ } else if (marginX !== undefined) {
18626
+ style.marginRight = marginX;
18627
+ }
18628
+ if (marginTop !== undefined) {
18629
+ style.marginTop = marginTop;
18630
+ } else if (marginY !== undefined) {
18631
+ style.marginTop = marginY;
18632
+ }
18633
+ if (marginBottom !== undefined) {
18634
+ style.marginBottom = marginBottom;
18635
+ } else if (marginY !== undefined) {
18636
+ style.marginBottom = marginY;
18637
+ }
18638
+ }
18639
+ {
18640
+ const padding = props.padding;
18641
+ const paddingX = props.paddingX;
18642
+ const paddingY = props.paddingY;
18643
+ const paddingLeft = props.paddingLeft;
18644
+ const paddingRight = props.paddingRight;
18645
+ const paddingTop = props.paddingTop;
18646
+ const paddingBottom = props.paddingBottom;
18647
+ delete props.padding;
18648
+ delete props.paddingX;
18649
+ delete props.paddingY;
18650
+ delete props.paddingLeft;
18651
+ delete props.paddingRight;
18652
+ delete props.paddingTop;
18653
+ delete props.paddingBottom;
18654
+ if (padding !== undefined) {
18655
+ style.padding = padding;
18656
+ }
18657
+ if (paddingLeft !== undefined) {
18658
+ style.paddingLeft = paddingLeft;
18659
+ } else if (paddingX !== undefined) {
18660
+ style.paddingLeft = paddingX;
18661
+ }
18662
+ if (paddingRight !== undefined) {
18663
+ style.paddingRight = paddingRight;
18664
+ } else if (paddingX !== undefined) {
18665
+ style.paddingRight = paddingX;
18666
+ }
18667
+ if (paddingTop !== undefined) {
18668
+ style.paddingTop = paddingTop;
18669
+ } else if (paddingY !== undefined) {
18670
+ style.paddingTop = paddingY;
18671
+ }
18672
+ if (paddingBottom !== undefined) {
18673
+ style.paddingBottom = paddingBottom;
18674
+ } else if (paddingY !== undefined) {
18675
+ style.paddingBottom = paddingY;
18676
+ }
18665
18677
  }
18666
18678
  return style;
18667
18679
  };
@@ -21352,6 +21364,153 @@ const Editable = forwardRef((props, ref) => {
21352
21364
  });
21353
21365
  });
21354
21366
 
21367
+ installImportMetaCss(import.meta);import.meta.css = /* css */`
21368
+ .navi_flex_row {
21369
+ display: flex;
21370
+ flex-direction: row;
21371
+ align-items: center;
21372
+ gap: 0;
21373
+ }
21374
+
21375
+ .navi_flex_column {
21376
+ display: flex;
21377
+ flex-direction: column;
21378
+ align-items: center;
21379
+ gap: 0;
21380
+ }
21381
+
21382
+ .navi_flex_item {
21383
+ flex-shrink: 0;
21384
+ }
21385
+ `;
21386
+ const FlexDirectionContext = createContext();
21387
+ const FlexRow = ({
21388
+ alignY,
21389
+ gap,
21390
+ style,
21391
+ children,
21392
+ ...rest
21393
+ }) => {
21394
+ const innerStyle = withPropsStyle({
21395
+ alignItems: alignY,
21396
+ gap,
21397
+ ...consumeSpacingProps(rest)
21398
+ }, style);
21399
+ return jsx("div", {
21400
+ ...rest,
21401
+ className: "navi_flex_row",
21402
+ style: innerStyle,
21403
+ children: jsx(FlexDirectionContext.Provider, {
21404
+ value: "row",
21405
+ children: children
21406
+ })
21407
+ });
21408
+ };
21409
+ const FlexColumn = ({
21410
+ alignX,
21411
+ gap,
21412
+ style,
21413
+ children,
21414
+ ...rest
21415
+ }) => {
21416
+ const innerStyle = withPropsStyle({
21417
+ alignItems: alignX,
21418
+ gap,
21419
+ ...consumeSpacingProps(rest)
21420
+ }, style);
21421
+ return jsx("div", {
21422
+ ...rest,
21423
+ className: "navi_flex_column",
21424
+ style: innerStyle,
21425
+ children: jsx(FlexDirectionContext.Provider, {
21426
+ value: "column",
21427
+ children: children
21428
+ })
21429
+ });
21430
+ };
21431
+ const useConsumAlignProps = props => {
21432
+ const flexDirection = useContext(FlexDirectionContext);
21433
+ const alignX = props.alignX;
21434
+ const alignY = props.alignY;
21435
+ delete props.alignX;
21436
+ delete props.alignY;
21437
+ const style = {};
21438
+ if (flexDirection === "row") {
21439
+ // In row direction: alignX controls justify-content, alignY controls align-self
21440
+ // Default alignY is "center" from CSS, so only set alignSelf when different
21441
+ if (alignY !== undefined && alignY !== "center") {
21442
+ style.alignSelf = alignY;
21443
+ }
21444
+ // For row, alignX uses auto margins for positioning
21445
+ // NOTE: Auto margins only work effectively for positioning individual items.
21446
+ // When multiple adjacent items have the same auto margin alignment (e.g., alignX="end"),
21447
+ // only the first item will be positioned as expected because subsequent items
21448
+ // will be positioned relative to the previous item's margins, not the container edge.
21449
+ if (alignX !== undefined) {
21450
+ if (alignX === "start") {
21451
+ style.marginRight = "auto";
21452
+ } else if (alignX === "end") {
21453
+ style.marginLeft = "auto";
21454
+ } else if (alignX === "center") {
21455
+ style.marginLeft = "auto";
21456
+ style.marginRight = "auto";
21457
+ }
21458
+ }
21459
+ } else if (flexDirection === "column") {
21460
+ // In column direction: alignX controls align-self, alignY uses auto margins
21461
+ // Default alignX is "center" from CSS, so only set alignSelf when different
21462
+ if (alignX !== undefined && alignX !== "center") {
21463
+ style.alignSelf = alignX;
21464
+ }
21465
+ // For column, alignY uses auto margins for positioning
21466
+ // NOTE: Same auto margin limitation applies - multiple adjacent items with
21467
+ // the same alignY won't all position relative to container edges.
21468
+ if (alignY !== undefined) {
21469
+ if (alignY === "start") {
21470
+ style.marginBottom = "auto";
21471
+ } else if (alignY === "end") {
21472
+ style.marginTop = "auto";
21473
+ } else if (alignY === "center") {
21474
+ style.marginTop = "auto";
21475
+ style.marginBottom = "auto";
21476
+ }
21477
+ }
21478
+ }
21479
+ return style;
21480
+ };
21481
+ const FlexItem = ({
21482
+ alignX,
21483
+ alignY,
21484
+ grow,
21485
+ shrink,
21486
+ className,
21487
+ style,
21488
+ children,
21489
+ ...rest
21490
+ }) => {
21491
+ const flexDirection = useContext(FlexDirectionContext);
21492
+ if (!flexDirection) {
21493
+ console.warn("FlexItem must be used within a FlexRow or FlexColumn component.");
21494
+ }
21495
+ const innerClassName = withPropsClassName("navi_flex_item", className);
21496
+ const alignStyle = useConsumAlignProps({
21497
+ alignX,
21498
+ alignY
21499
+ });
21500
+ const innerStyle = withPropsStyle({
21501
+ flexGrow: grow ? 1 : undefined,
21502
+ flexShrink: shrink ? 1 : undefined,
21503
+ ...consumeSpacingProps(rest),
21504
+ ...alignStyle
21505
+ }, style);
21506
+ return jsx("div", {
21507
+ ...rest,
21508
+ className: innerClassName,
21509
+ style: innerStyle,
21510
+ children: children
21511
+ });
21512
+ };
21513
+
21355
21514
  const useFormEvents = (
21356
21515
  elementRef,
21357
21516
  {
@@ -21573,7 +21732,8 @@ const ButtonBasic = forwardRef((props, ref) => {
21573
21732
  autoFocus,
21574
21733
  // visual
21575
21734
  appearance = "navi",
21576
- alignX = "start",
21735
+ alignX,
21736
+ alignY,
21577
21737
  discrete,
21578
21738
  className,
21579
21739
  style,
@@ -21599,13 +21759,9 @@ const ButtonBasic = forwardRef((props, ref) => {
21599
21759
  const innerClassName = withPropsClassName(appearance === "navi" ? "navi_button" : undefined, className);
21600
21760
  const innerStyle = withPropsStyle({
21601
21761
  ...consumeSpacingProps(rest),
21602
- ...(alignX === "start" ? {} : alignX === "center" ? {
21603
- alignSelf: "center",
21604
- marginLeft: "auto",
21605
- marginRight: "auto"
21606
- } : {
21607
- alignSelf: "end",
21608
- marginRight: "auto"
21762
+ ...useConsumAlignProps({
21763
+ alignX,
21764
+ alignY
21609
21765
  })
21610
21766
  }, style);
21611
21767
  return jsx("button", {
@@ -22171,6 +22327,7 @@ const FormWithAction = forwardRef((props, ref) => {
22171
22327
  method,
22172
22328
  actionErrorEffect = "show_validation_message",
22173
22329
  // "show_validation_message" or "throw"
22330
+ errorMapping,
22174
22331
  onActionPrevented,
22175
22332
  onActionStart,
22176
22333
  onActionAbort,
@@ -22184,7 +22341,8 @@ const FormWithAction = forwardRef((props, ref) => {
22184
22341
  useImperativeHandle(ref, () => innerRef.current);
22185
22342
  const [actionBoundToUIState] = useActionBoundToOneParam(action, uiState);
22186
22343
  const executeAction = useExecuteAction(innerRef, {
22187
- errorEffect: actionErrorEffect
22344
+ errorEffect: actionErrorEffect,
22345
+ errorMapping
22188
22346
  });
22189
22347
  const {
22190
22348
  actionPending,
@@ -28053,6 +28211,7 @@ const Text = ({
28053
28211
  italic,
28054
28212
  underline,
28055
28213
  style,
28214
+ alignX,
28056
28215
  ...rest
28057
28216
  }) => {
28058
28217
  const innerStyle = withPropsStyle({
@@ -28060,7 +28219,15 @@ const Text = ({
28060
28219
  fontWeight: bold ? "bold" : undefined,
28061
28220
  fontStyle: italic ? "italic" : undefined,
28062
28221
  textDecoration: underline ? "underline" : undefined,
28063
- ...consumeSpacingProps(rest)
28222
+ ...consumeSpacingProps(rest),
28223
+ ...(alignX === "start" ? {} : alignX === "center" ? {
28224
+ alignSelf: "center",
28225
+ marginLeft: "auto",
28226
+ marginRight: "auto"
28227
+ } : {
28228
+ alignSelf: "end",
28229
+ marginLeft: "auto"
28230
+ })
28064
28231
  }, style);
28065
28232
  return jsx("span", {
28066
28233
  ...rest,
@@ -28203,4 +28370,4 @@ const useDependenciesDiff = (inputs) => {
28203
28370
  return diffRef.current;
28204
28371
  };
28205
28372
 
28206
- export { ActionRenderer, ActiveKeyboardShortcuts, Button, Checkbox, CheckboxList, Col, Colgroup, Details, Editable, ErrorBoundaryContext, FontSizedSvg, Form, Icon, IconAndText, Input, Label, Link, LinkWithIcon, Overflow, Radio, RadioList, Route, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, Select, SelectionContext, Spacing, SummaryMarker, Tab, TabList, Table, TableCell, Tbody, Text, TextAndCount, Thead, Tr, UITransition, actionIntegratedVia, addCustomMessage, createAction, createSelectionKeyboardShortcuts, createUniqueValueConstraint, defineRoutes, enableDebugActions, enableDebugOnDocumentLoading, goBack, goForward, goTo, isCellSelected, isColumnSelected, isRowSelected, openCallout, rawUrlPart, reload, removeCustomMessage, rerunActions, resource, setBaseUrl, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useCellsAndColumns, useDependenciesDiff, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, valueInLocalStorage };
28373
+ export { ActionRenderer, ActiveKeyboardShortcuts, Button, Checkbox, CheckboxList, Col, Colgroup, Details, Editable, ErrorBoundaryContext, FlexColumn, FlexItem, FlexRow, FontSizedSvg, Form, Icon, IconAndText, Input, Label, Link, LinkWithIcon, Overflow, Radio, RadioList, Route, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, Select, SelectionContext, Spacing, SummaryMarker, Tab, TabList, Table, TableCell, Tbody, Text, TextAndCount, Thead, Tr, UITransition, actionIntegratedVia, addCustomMessage, createAction, createSelectionKeyboardShortcuts, createUniqueValueConstraint, defineRoutes, enableDebugActions, enableDebugOnDocumentLoading, goBack, goForward, goTo, isCellSelected, isColumnSelected, isRowSelected, openCallout, rawUrlPart, reload, removeCustomMessage, rerunActions, resource, setBaseUrl, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useCellsAndColumns, useDependenciesDiff, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, valueInLocalStorage };
package/index.js CHANGED
@@ -93,6 +93,11 @@ export { TextAndCount } from "./src/components/text/text_and_count.jsx";
93
93
  // Callout, dialogs, ...
94
94
  export { openCallout } from "./src/components/callout/callout.js";
95
95
  // Layout
96
+ export {
97
+ FlexColumn,
98
+ FlexItem,
99
+ FlexRow,
100
+ } from "./src/components/layout/flex.jsx";
96
101
  export { Spacing } from "./src/components/layout/spacing.jsx";
97
102
 
98
103
  // Validation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Library of components including navigation to create frontend applications",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,6 +11,7 @@ export const useExecuteAction = (
11
11
  elementRef,
12
12
  {
13
13
  errorEffect = "show_validation_message", // "show_validation_message" or "throw"
14
+ errorMapping,
14
15
  } = {},
15
16
  ) => {
16
17
  // see https://medium.com/trabe/catching-asynchronous-errors-in-react-using-error-boundaries-5e8a5fd7b971
@@ -28,7 +29,8 @@ export const useExecuteAction = (
28
29
  const validationMessageTargetRef = useRef(null);
29
30
  const addErrorMessage = (error) => {
30
31
  const validationMessageTarget = validationMessageTargetRef.current;
31
- addCustomMessage(validationMessageTarget, "action_error", error, {
32
+ const errorMapped = errorMapping ? errorMapping(error) : error;
33
+ addCustomMessage(validationMessageTarget, "action_error", errorMapped, {
32
34
  // This error should not prevent <form> submission
33
35
  // so whenever user tries to submit the form the error is cleared
34
36
  // (Hitting enter key, clicking on submit button, etc. would allow to re-submit the form in error state)
@@ -48,7 +48,7 @@ export const openCallout = (
48
48
  level = "warning",
49
49
  onClose,
50
50
  closeOnClickOutside = level === "info",
51
- hideErrorStack,
51
+ showErrorStack,
52
52
  debug = false,
53
53
  } = {},
54
54
  ) => {
@@ -116,7 +116,7 @@ export const openCallout = (
116
116
  if (Error.isError(newMessage)) {
117
117
  const error = newMessage;
118
118
  newMessage = error.message;
119
- if (!hideErrorStack && error.stack) {
119
+ if (showErrorStack && error.stack) {
120
120
  newMessage += `<pre class="navi_callout_error_stack">${escapeHtml(String(error.stack))}</pre>`;
121
121
  }
122
122
  }
@@ -14,6 +14,7 @@ import { FormActionContext } from "../action_execution/form_context.js";
14
14
  import { renderActionableComponent } from "../action_execution/render_actionable_component.jsx";
15
15
  import { useAction } from "../action_execution/use_action.js";
16
16
  import { useExecuteAction } from "../action_execution/use_execute_action.js";
17
+ import { useConsumAlignProps } from "../layout/flex.jsx";
17
18
  import { consumeSpacingProps } from "../layout/spacing.jsx";
18
19
  import { LoaderBackground } from "../loader/loader_background.jsx";
19
20
  import { withPropsClassName } from "../props_composition/with_props_class_name.js";
@@ -211,7 +212,8 @@ const ButtonBasic = forwardRef((props, ref) => {
211
212
 
212
213
  // visual
213
214
  appearance = "navi",
214
- alignX = "start",
215
+ alignX,
216
+ alignY,
215
217
  discrete,
216
218
  className,
217
219
  style,
@@ -243,18 +245,7 @@ const ButtonBasic = forwardRef((props, ref) => {
243
245
  const innerStyle = withPropsStyle(
244
246
  {
245
247
  ...consumeSpacingProps(rest),
246
- ...(alignX === "start"
247
- ? {}
248
- : alignX === "center"
249
- ? {
250
- alignSelf: "center",
251
- marginLeft: "auto",
252
- marginRight: "auto",
253
- }
254
- : {
255
- alignSelf: "end",
256
- marginRight: "auto",
257
- }),
248
+ ...useConsumAlignProps({ alignX, alignY }),
258
249
  },
259
250
  style,
260
251
  );
@@ -121,6 +121,7 @@ const FormWithAction = forwardRef((props, ref) => {
121
121
  action,
122
122
  method,
123
123
  actionErrorEffect = "show_validation_message", // "show_validation_message" or "throw"
124
+ errorMapping,
124
125
  onActionPrevented,
125
126
  onActionStart,
126
127
  onActionAbort,
@@ -135,6 +136,7 @@ const FormWithAction = forwardRef((props, ref) => {
135
136
  const [actionBoundToUIState] = useActionBoundToOneParam(action, uiState);
136
137
  const executeAction = useExecuteAction(innerRef, {
137
138
  errorEffect: actionErrorEffect,
139
+ errorMapping,
138
140
  });
139
141
  const { actionPending, actionRequester: formActionRequester } =
140
142
  useRequestedActionStatus(innerRef);