@jsenv/navi 0.12.20 → 0.12.21

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,5 +1,5 @@
1
1
  import { installImportMetaCss } from "./jsenv_navi_side_effects.js";
2
- import { createIterableWeakSet, createPubSub, createValueEffect, createStyleController, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, resolveCSSColor, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, activeElementSignal, canInterceptKeys, createGroupTransitionController, getElementSignature, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, mergeOneStyle, stringifyStyle, mergeTwoStyles, normalizeStyles, resolveCSSSize, findBefore, findAfter, initFocusGroup, elementIsFocusable, pickLightOrDark, resolveColorLuminance, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
2
+ import { createIterableWeakSet, createPubSub, createValueEffect, createStyleController, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, resolveCSSColor, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, activeElementSignal, canInterceptKeys, createGroupTransitionController, getElementSignature, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, stringifyStyle, mergeOneStyle, mergeTwoStyles, normalizeStyles, resolveCSSSize, findBefore, findAfter, hasCSSSizeUnit, initFocusGroup, elementIsFocusable, pickLightOrDark, resolveColorLuminance, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
3
3
  import { prefixFirstAndIndentRemainingLines } from "@jsenv/humanize";
4
4
  import { effect, signal, computed, batch, useSignal } from "@preact/signals";
5
5
  import { useEffect, useRef, useCallback, useContext, useState, useLayoutEffect, useMemo, useErrorBoundary, useImperativeHandle, useId } from "preact/hooks";
@@ -10401,6 +10401,8 @@ const POSITION_PROPS = {
10401
10401
  },
10402
10402
  left: PASS_THROUGH,
10403
10403
  top: PASS_THROUGH,
10404
+ bottom: PASS_THROUGH,
10405
+ right: PASS_THROUGH,
10404
10406
 
10405
10407
  translateX: (value) => {
10406
10408
  return { transform: `translateX(${value})` };
@@ -10442,7 +10444,7 @@ const POSITION_PROPS = {
10442
10444
  },
10443
10445
  };
10444
10446
  const TYPO_PROPS = {
10445
- font: PASS_THROUGH,
10447
+ font: applyOnCSSProp("fontFamily"),
10446
10448
  fontFamily: PASS_THROUGH,
10447
10449
  size: applyOnCSSProp("fontSize"),
10448
10450
  fontSize: PASS_THROUGH,
@@ -10521,9 +10523,9 @@ const CONTENT_PROPS = {
10521
10523
  spacing: (value, { layout }) => {
10522
10524
  if (
10523
10525
  layout === "row" ||
10524
- layout === "column"
10525
- // layout === "inline-row" ||
10526
- // layout === "inline-column"
10526
+ layout === "column" ||
10527
+ layout === "inline-row" ||
10528
+ layout === "inline-column"
10527
10529
  ) {
10528
10530
  return {
10529
10531
  gap: resolveSpacingSize(value, "gap"),
@@ -10644,19 +10646,29 @@ const sizeSpacingScale = {
10644
10646
  xl: "2em", // 2 = 32px at 16px base
10645
10647
  xxl: "3em", // 3 = 48px at 16px base
10646
10648
  };
10649
+ sizeSpacingScale.s = sizeSpacingScale.sm;
10650
+ sizeSpacingScale.m = sizeSpacingScale.md;
10651
+ sizeSpacingScale.l = sizeSpacingScale.lg;
10652
+ const sizeSpacingScaleKeys = new Set(Object.keys(sizeSpacingScale));
10653
+ const isSizeSpacingScaleKey = (key) => {
10654
+ return sizeSpacingScaleKeys.has(key);
10655
+ };
10647
10656
  const resolveSpacingSize = (size, property = "padding") => {
10648
10657
  return stringifyStyle(sizeSpacingScale[size] || size, property);
10649
10658
  };
10650
10659
 
10651
10660
  const sizeTypoScale = {
10652
- xxs: "0.625em", // 0.625 = 10px at 16px base (smaller than before for more range)
10653
- xs: "0.75em", // 0.75 = 12px at 16px base
10654
- sm: "0.875em", // 0.875 = 14px at 16px base
10655
- md: "1em", // 1 = 16px at 16px base (base font size)
10656
- lg: "1.125em", // 1.125 = 18px at 16px base
10657
- xl: "1.25em", // 1.25 = 20px at 16px base
10658
- xxl: "1.5em", // 1.5 = 24px at 16px base
10659
- };
10661
+ xxs: "0.625rem", // 0.625 = 10px at 16px base (smaller than before for more range)
10662
+ xs: "0.75rem", // 0.75 = 12px at 16px base
10663
+ sm: "0.875rem", // 0.875 = 14px at 16px base
10664
+ md: "1rem", // 1 = 16px at 16px base (base font size)
10665
+ lg: "1.125rem", // 1.125 = 18px at 16px base
10666
+ xl: "1.25rem", // 1.25 = 20px at 16px base
10667
+ xxl: "1.5rem", // 1.5 = 24px at 16px base
10668
+ };
10669
+ sizeTypoScale.s = sizeTypoScale.sm;
10670
+ sizeTypoScale.m = sizeTypoScale.md;
10671
+ sizeTypoScale.l = sizeTypoScale.lg;
10660
10672
 
10661
10673
  const DEFAULT_DISPLAY_BY_TAG_NAME = {
10662
10674
  "inline": new Set([
@@ -11264,9 +11276,6 @@ const Box = props => {
11264
11276
  row,
11265
11277
  column
11266
11278
  } = rest;
11267
- if (box === "auto") {
11268
- box = Boolean(rest.alignX || rest.alignY);
11269
- }
11270
11279
  if (box) {
11271
11280
  if (inline === undefined) {
11272
11281
  inline = true;
@@ -13645,13 +13654,25 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
13645
13654
  text-overflow: ellipsis;
13646
13655
  overflow: hidden;
13647
13656
  }
13657
+
13658
+ .navi_custom_space {
13659
+ }
13648
13660
  `;
13649
- const INSERTED_SPACE = jsx("span", {
13661
+ const REGULAR_SPACE = jsx("span", {
13650
13662
  "data-navi-space": "",
13651
13663
  children: " "
13652
13664
  });
13665
+ const CustomWidthSpace = ({
13666
+ value
13667
+ }) => {
13668
+ return jsx("span", {
13669
+ className: "navi_custom_space",
13670
+ style: `padding-left: ${value}`,
13671
+ children: "\u200B"
13672
+ });
13673
+ };
13653
13674
  const applySpacingOnTextChildren = (children, spacing) => {
13654
- if (spacing === "pre") {
13675
+ if (spacing === "pre" || spacing === "0" || spacing === 0) {
13655
13676
  return children;
13656
13677
  }
13657
13678
  if (!children) {
@@ -13662,23 +13683,28 @@ const applySpacingOnTextChildren = (children, spacing) => {
13662
13683
  if (childCount <= 1) {
13663
13684
  return children;
13664
13685
  }
13665
-
13666
- // Helper function to check if a value ends with whitespace
13667
- const endsWithWhitespace = value => {
13668
- if (typeof value === "string") {
13669
- return /\s$/.test(value);
13670
- }
13671
- return false;
13672
- };
13673
-
13674
- // Helper function to check if a value starts with whitespace
13675
- const startsWithWhitespace = value => {
13676
- if (typeof value === "string") {
13677
- return /^\s/.test(value);
13686
+ let separator;
13687
+ if (spacing === undefined) {
13688
+ spacing = REGULAR_SPACE;
13689
+ } else if (typeof spacing === "string") {
13690
+ if (isSizeSpacingScaleKey(spacing)) {
13691
+ separator = jsx(CustomWidthSpace, {
13692
+ value: resolveSpacingSize(spacing)
13693
+ });
13694
+ } else if (hasCSSSizeUnit(spacing)) {
13695
+ separator = jsx(CustomWidthSpace, {
13696
+ value: resolveSpacingSize(spacing)
13697
+ });
13698
+ } else {
13699
+ separator = spacing;
13678
13700
  }
13679
- return false;
13680
- };
13681
- const separator = spacing === undefined || spacing === " " ? INSERTED_SPACE : spacing;
13701
+ } else if (typeof spacing === "number") {
13702
+ separator = jsx(CustomWidthSpace, {
13703
+ value: spacing
13704
+ });
13705
+ } else {
13706
+ separator = spacing;
13707
+ }
13682
13708
  const childrenWithGap = [];
13683
13709
  let i = 0;
13684
13710
  while (true) {
@@ -13688,17 +13714,30 @@ const applySpacingOnTextChildren = (children, spacing) => {
13688
13714
  if (i === childCount) {
13689
13715
  break;
13690
13716
  }
13691
-
13692
- // Check if we should skip adding spacing
13693
13717
  const currentChild = childArray[i - 1];
13694
13718
  const nextChild = childArray[i];
13695
- const shouldSkipSpacing = endsWithWhitespace(currentChild) || startsWithWhitespace(nextChild);
13696
- if (!shouldSkipSpacing) {
13697
- childrenWithGap.push(separator);
13719
+ if (endsWithWhitespace(currentChild)) {
13720
+ continue;
13721
+ }
13722
+ if (startsWithWhitespace(nextChild)) {
13723
+ continue;
13698
13724
  }
13725
+ childrenWithGap.push(separator);
13699
13726
  }
13700
13727
  return childrenWithGap;
13701
13728
  };
13729
+ const endsWithWhitespace = jsxChild => {
13730
+ if (typeof jsxChild === "string") {
13731
+ return /\s$/.test(jsxChild);
13732
+ }
13733
+ return false;
13734
+ };
13735
+ const startsWithWhitespace = jsxChild => {
13736
+ if (typeof jsxChild === "string") {
13737
+ return /^\s/.test(jsxChild);
13738
+ }
13739
+ return false;
13740
+ };
13702
13741
  const OverflowPinnedElementContext = createContext(null);
13703
13742
  const Text = props => {
13704
13743
  const {
@@ -13812,6 +13851,7 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
13812
13851
  .navi_icon_foreground > .navi_text {
13813
13852
  display: flex;
13814
13853
  aspect-ratio: 1 / 1;
13854
+ min-width: 0;
13815
13855
  height: 100%;
13816
13856
  max-height: 1em;
13817
13857
  align-items: center;
@@ -13906,12 +13946,10 @@ const Icon = ({
13906
13946
  className: "navi_icon_char_slot",
13907
13947
  "aria-hidden": "true",
13908
13948
  children: invisibleText
13909
- }), jsx("span", {
13949
+ }), jsx(Text, {
13910
13950
  className: "navi_icon_foreground",
13911
- children: jsx(Text, {
13912
- spacing: "pre",
13913
- children: innerChildren
13914
- })
13951
+ spacing: "pre",
13952
+ children: innerChildren
13915
13953
  })]
13916
13954
  });
13917
13955
  };
@@ -13943,6 +13981,7 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
13943
13981
  position: relative;
13944
13982
  color: var(--x-link-color);
13945
13983
  text-decoration: var(--x-link-text-decoration);
13984
+ vertical-align: middle;
13946
13985
  border-radius: var(--link-border-radius);
13947
13986
  outline-color: var(--link-outline-color);
13948
13987
  cursor: var(--x-link-cursor);
@@ -14103,7 +14142,6 @@ const LinkPlain = props => {
14103
14142
  innerIcon = icon;
14104
14143
  }
14105
14144
  return jsxs(Box, {
14106
- box: "auto",
14107
14145
  ...rest,
14108
14146
  ref: ref,
14109
14147
  as: "a",
@@ -16785,9 +16823,9 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
16785
16823
  --x-button-color: var(--button-color);
16786
16824
 
16787
16825
  position: relative;
16788
- /* display: inline-flex; */
16789
16826
  box-sizing: border-box;
16790
16827
  padding: 0;
16828
+ vertical-align: middle;
16791
16829
  background: none;
16792
16830
  border: none;
16793
16831
  border-radius: var(--x-button-border-radius);
@@ -16997,7 +17035,6 @@ const ButtonBasic = props => {
16997
17035
  };
16998
17036
  const renderButtonContentMemoized = useCallback(renderButtonContent, []);
16999
17037
  return jsxs(Box, {
17000
- box: "auto",
17001
17038
  ...rest,
17002
17039
  as: "button",
17003
17040
  ref: ref,
@@ -21554,17 +21591,24 @@ const SVGMaskOverlay = ({
21554
21591
  });
21555
21592
  };
21556
21593
 
21557
- const CSS_VAR_NAME = "--color-contrasting";
21594
+ const CSS_VAR_NAME = "--x-color-contrasting";
21558
21595
 
21559
- const useContrastingColor = (ref) => {
21596
+ const useContrastingColor = (ref, backgroundElementSelector) => {
21560
21597
  useLayoutEffect(() => {
21561
21598
  const el = ref.current;
21562
21599
  if (!el) {
21563
21600
  return;
21564
21601
  }
21602
+ let elementToCheck = el;
21603
+ {
21604
+ elementToCheck = el.querySelector(backgroundElementSelector);
21605
+ if (!elementToCheck) {
21606
+ return;
21607
+ }
21608
+ }
21565
21609
  const lightColor = "var(--navi-color-light)";
21566
21610
  const darkColor = "var(--navi-color-dark)";
21567
- const backgroundColor = getComputedStyle(el).backgroundColor;
21611
+ const backgroundColor = getComputedStyle(elementToCheck).backgroundColor;
21568
21612
  if (!backgroundColor) {
21569
21613
  el.style.removeProperty(CSS_VAR_NAME);
21570
21614
  return;
@@ -21581,45 +21625,65 @@ const useContrastingColor = (ref) => {
21581
21625
 
21582
21626
  installImportMetaCss(import.meta);import.meta.css = /* css */`
21583
21627
  @layer navi {
21584
- .navi_badge_count {
21585
- --border-radius: 1em;
21586
- }
21587
21628
  }
21588
21629
  .navi_badge_count {
21589
- --spacing: 0.3em;
21590
- --size: 1em;
21591
- --x-outer-size: calc(var(--size) + var(--spacing));
21592
- --x-offset-top: calc(0.5 * (var(--x-outer-size) - 1em));
21593
-
21630
+ --x-size: 1.5em;
21631
+ --x-border-radius: var(--border-radius);
21632
+ --x-number-font-size: var(--font-size);
21633
+ position: relative;
21594
21634
  display: inline-block;
21595
- box-sizing: border-box;
21596
- min-width: var(--x-outer-size);
21597
- height: var(--x-outer-size);
21598
- max-height: var(--x-outer-size);
21599
- margin-top: calc(-1 * var(--x-offset-top));
21600
- padding-right: var(--spacing);
21601
- padding-left: var(--spacing);
21602
- color: var(--color, var(--color-contrasting));
21603
- text-align: center;
21604
- line-height: var(--x-outer-size);
21605
- /* vertical-align: middle; */
21635
+
21636
+ color: var(--color, var(--x-color-contrasting));
21637
+ font-size: var(--font-size);
21638
+ vertical-align: middle;
21639
+ border-radius: var(--x-border-radius);
21640
+ }
21641
+ .navi_count_badge_overflow {
21642
+ position: relative;
21643
+ top: -0.1em;
21644
+ }
21645
+ /* Ellipse */
21646
+ .navi_badge_count[data-ellipse] {
21647
+ padding-right: 0.4em;
21648
+ padding-left: 0.4em;
21649
+ background: var(--background);
21650
+ background-color: var(--background-color, var(--background));
21651
+ border-radius: 1em;
21652
+ }
21653
+ /* Circle */
21654
+ .navi_badge_count[data-circle] {
21655
+ width: var(--x-size);
21656
+ height: var(--x-size);
21657
+ }
21658
+ .navi_badge_count_frame {
21659
+ position: absolute;
21660
+ top: 50%;
21661
+ left: 0;
21662
+ width: 100%;
21663
+ height: 100%;
21606
21664
  background: var(--background);
21607
21665
  background-color: var(--background-color, var(--background));
21608
- border-radius: var(--border-radius, 1em);
21666
+ border-radius: inherit;
21667
+ transform: translateY(-50%);
21609
21668
  }
21610
- .navi_badge_count[data-single-digit] {
21611
- --spacing: 0em;
21612
- --size: 1.3em;
21669
+ .navi_badge_count_text {
21670
+ position: absolute;
21671
+ top: 50%;
21672
+ left: 50%;
21673
+ font-size: var(--x-number-font-size, inherit);
21674
+ transform: translate(-50%, -50%);
21613
21675
  }
21614
- .navi_badge_count[data-two-digits] {
21615
- --spacing: 0em;
21616
- --size: 1.6em;
21676
+ .navi_badge_count[data-single-char] {
21677
+ --x-border-radius: 100%;
21678
+ --x-number-font-size: unset;
21617
21679
  }
21618
-
21619
- .navi_count_badge_overflow {
21620
- position: relative;
21621
- top: -0.4em;
21622
- font-size: 0.8em;
21680
+ .navi_badge_count[data-two-chars] {
21681
+ --x-border-radius: 100%;
21682
+ --x-number-font-size: 0.8em;
21683
+ }
21684
+ .navi_badge_count[data-three-chars] {
21685
+ --x-border-radius: 100%;
21686
+ --x-number-font-size: 0.6em;
21623
21687
  }
21624
21688
  `;
21625
21689
  const BadgeStyleCSSVars = {
@@ -21630,50 +21694,121 @@ const BadgeStyleCSSVars = {
21630
21694
  backgroundColor: "--background-color",
21631
21695
  background: "--background",
21632
21696
  borderColor: "--border-color",
21633
- color: "--color"
21697
+ color: "--color",
21698
+ fontSize: "--font-size"
21634
21699
  };
21635
21700
  const BadgeCountOverflow = () => jsx("span", {
21636
21701
  className: "navi_count_badge_overflow",
21637
21702
  children: "+"
21638
21703
  });
21704
+ const MAX_CHAR_AS_CIRCLE = 3;
21639
21705
  const BadgeCount = ({
21640
21706
  children,
21641
- max,
21707
+ max = 99,
21642
21708
  maxElement = jsx(BadgeCountOverflow, {}),
21709
+ // When you use max="none" (or max > 99) it might be a good idea to force ellipse
21710
+ // so that visually the interface do not suddently switch from circle to ellipse depending on the count
21711
+ ellipse,
21643
21712
  ...props
21644
21713
  }) => {
21645
21714
  const defaultRef = useRef();
21646
21715
  const ref = props.ref || defaultRef;
21647
- const numericValue = typeof children === "string" ? parseInt(children, 10) : children;
21648
- // Calculer la valeur à afficher en fonction du paramètre max
21649
- const getDisplayValue = () => {
21650
- if (max === undefined) {
21651
- return children;
21652
- }
21653
- const numericMax = typeof max === "string" ? parseInt(max, 10) : max;
21654
- if (isNaN(numericValue) || isNaN(numericMax)) {
21655
- return children;
21656
- }
21657
- if (numericValue > numericMax) {
21658
- return jsxs(Fragment, {
21659
- children: [children, maxElement]
21660
- });
21661
- }
21662
- return children;
21663
- };
21664
- const displayValue = getDisplayValue();
21665
- useContrastingColor(ref);
21666
- const digitCount = String(numericValue).length;
21667
- return jsx(Text, {
21716
+ useContrastingColor(ref, ".navi_badge_count_visual");
21717
+ const valueRequested = typeof children === "string" ? parseInt(children, 10) : children;
21718
+ const valueDisplayed = applyMaxToValue(max, valueRequested);
21719
+ const hasOverflow = valueDisplayed !== valueRequested;
21720
+ const valueCharCount = String(valueDisplayed).length;
21721
+ const charCount = valueCharCount + (hasOverflow ? 1 : 0);
21722
+ if (charCount > MAX_CHAR_AS_CIRCLE) {
21723
+ ellipse = true;
21724
+ }
21725
+ if (ellipse) {
21726
+ return jsxs(BadgeCountEllipse, {
21727
+ ...props,
21728
+ ref: ref,
21729
+ hasOverflow: hasOverflow,
21730
+ children: [valueDisplayed, hasOverflow && maxElement]
21731
+ });
21732
+ }
21733
+ return jsxs(BadgeCountCircle, {
21734
+ ...props,
21735
+ ref: ref,
21736
+ hasOverflow: hasOverflow,
21737
+ charCount: charCount,
21738
+ children: [valueDisplayed, hasOverflow && maxElement]
21739
+ });
21740
+ };
21741
+ const applyMaxToValue = (max, value) => {
21742
+ if (isNaN(value)) {
21743
+ return value;
21744
+ }
21745
+ if (max === undefined || max === Infinity || max === false || max === "false" || max === "Infinity" || max === "none") {
21746
+ return value;
21747
+ }
21748
+ const numericMax = typeof max === "string" ? parseInt(max, 10) : max;
21749
+ if (isNaN(numericMax)) {
21750
+ return value;
21751
+ }
21752
+ if (value > numericMax) {
21753
+ return numericMax;
21754
+ }
21755
+ return value;
21756
+ };
21757
+ const BadgeCountCircle = ({
21758
+ ref,
21759
+ charCount,
21760
+ hasOverflow,
21761
+ children,
21762
+ ...props
21763
+ }) => {
21764
+ return jsxs(Text, {
21668
21765
  ref: ref,
21669
21766
  className: "navi_badge_count",
21767
+ "data-circle": "",
21670
21768
  bold: true,
21671
- "data-single-digit": digitCount === 1 ? "" : undefined,
21672
- "data-two-digits": digitCount === 2 ? "" : undefined,
21769
+ "data-single-char": charCount === 1 ? "" : undefined,
21770
+ "data-two-chars": charCount === 2 ? "" : undefined,
21771
+ "data-three-chars": charCount === 3 ? "" : undefined,
21772
+ "data-value-overflow": hasOverflow ? "" : undefined,
21673
21773
  ...props,
21674
21774
  styleCSSVars: BadgeStyleCSSVars,
21675
21775
  spacing: "pre",
21676
- children: displayValue
21776
+ children: [jsx("span", {
21777
+ style: "user-select: none",
21778
+ children: "\u200B"
21779
+ }), jsx("span", {
21780
+ className: "navi_badge_count_frame"
21781
+ }), jsx("span", {
21782
+ className: "navi_badge_count_text",
21783
+ children: children
21784
+ }), jsx("span", {
21785
+ style: "user-select: none",
21786
+ children: "\u200B"
21787
+ })]
21788
+ });
21789
+ };
21790
+ const BadgeCountEllipse = ({
21791
+ ref,
21792
+ children,
21793
+ hasOverflow,
21794
+ ...props
21795
+ }) => {
21796
+ return jsxs(Text, {
21797
+ ref: ref,
21798
+ className: "navi_badge_count",
21799
+ bold: true,
21800
+ "data-ellipse": "",
21801
+ "data-value-overflow": hasOverflow ? "" : undefined,
21802
+ ...props,
21803
+ styleCSSVars: BadgeStyleCSSVars,
21804
+ spacing: "pre",
21805
+ children: [jsx("span", {
21806
+ style: "user-select: none",
21807
+ children: "\u200B"
21808
+ }), children, jsx("span", {
21809
+ style: "user-select: none",
21810
+ children: "\u200B"
21811
+ })]
21677
21812
  });
21678
21813
  };
21679
21814