@jsenv/navi 0.29.30 → 0.29.32

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.
@@ -11763,7 +11763,11 @@ const readNumberFromDom = (element, attribute, defaultValue) => {
11763
11763
  * interaction_registry.js) — navi has no private one.
11764
11764
  *
11765
11765
  * `contextmenu` is the only one that takes something away: the browser's own menu
11766
- * would cover the answer to the request it is.
11766
+ * would cover the answer to the request it is. It takes it away AFTER, though —
11767
+ * a native interaction IS its own event, the very one the gate reads, and a gate
11768
+ * refuses what is already `defaultPrevented`. Cancelling first would therefore
11769
+ * make the interaction refuse itself, and no right click could ever get through.
11770
+ * So: ask, and only cover the browser's menu once something answered.
11767
11771
  */
11768
11772
 
11769
11773
 
@@ -11781,10 +11785,13 @@ defineInteractionDetector({
11781
11785
  setup: (element, trigger, { types }) => {
11782
11786
  const listeners = types.map((type) => {
11783
11787
  const listener = (nativeEvent) => {
11784
- if (type === "contextmenu") {
11788
+ const performed = trigger(type, nativeEvent);
11789
+ if (type === "contextmenu" && performed) {
11790
+ // Something answered the request, so the browser's own menu would only
11791
+ // cover it. Nothing answered (the gate refused, a listener said "not
11792
+ // this time"): the right click stays what it was, and opens the menu.
11785
11793
  nativeEvent.preventDefault();
11786
11794
  }
11787
- trigger(type, nativeEvent);
11788
11795
  };
11789
11796
  element.addEventListener(type, listener);
11790
11797
  return [type, listener];
@@ -11827,8 +11834,9 @@ installImportMetaCssBuild(import.meta);/**
11827
11834
  * that — and says where it is up to, for the caller to draw the rest with:
11828
11835
  *
11829
11836
  * - `--swipe-pulled`: how far it has come, signed, in px.
11830
- * - `--swipe-progress`: the same as a fraction of the element, signed. Inherited,
11831
- * so anything inside the element can read it.
11837
+ * - `--swipe-progress`: the same as a fraction of the element, signed.
11838
+ *
11839
+ * Both inherit, so anything inside the element can read them.
11832
11840
  * - `[data-swiping="left|right|up|down"]`: which way, while a finger holds it.
11833
11841
  * - `[data-swipe-past-threshold]`: letting go now would go through with it.
11834
11842
  *
@@ -11910,8 +11918,10 @@ const LONGPRESS_ATTRIBUTE = "data-longpress";
11910
11918
  import.meta.css = [/* css */`
11911
11919
  /* Declared, so the browser sees a NUMBER it can interpolate and calculate with:
11912
11920
  what a swipe reveals behind the element is drawn from this, and an undeclared
11913
- custom property only ever jumps from one value to the next. Inherited, so
11914
- anything inside the element can read it. */
11921
+ custom property only ever jumps from one value to the next. Both inherited,
11922
+ because what is drawn from them is drawn by a CHILD of the swiped element:
11923
+ the trail behind a row is inside the row, and a value that stopped at the
11924
+ element itself would reach 0 exactly where it is read. */
11915
11925
  @property --swipe-progress {
11916
11926
  syntax: "<number>";
11917
11927
  inherits: true;
@@ -11919,7 +11929,7 @@ import.meta.css = [/* css */`
11919
11929
  }
11920
11930
  @property --swipe-pulled {
11921
11931
  syntax: "<length>";
11922
- inherits: false;
11932
+ inherits: true;
11923
11933
  initial-value: 0px;
11924
11934
  }
11925
11935
 
@@ -12294,6 +12304,30 @@ const swipeTypeOf = (axis, pulled) => {
12294
12304
  * `view-transition-name` must be unique per document, so only the application can
12295
12305
  * name what moves.
12296
12306
  *
12307
+ * WHEN THE PRESS BECOMES A HOLD: `grab`.
12308
+ *
12309
+ * interactions={{ toss: remove, grab: () => navigator.vibrate?.(10) }}
12310
+ *
12311
+ * The three above all answer the RELEASE, and between the press and the release
12312
+ * there is one instant that counts for the hand making the gesture: the one where
12313
+ * the object stops being pressed and starts being held. `grab` is that instant,
12314
+ * and it is the same one whichever way the drag was entered — a finger held still,
12315
+ * a mouse travelled a few pixels.
12316
+ *
12317
+ * It matters most where it is least visible. On a screen the held object is under
12318
+ * the thumb that hides it, so the only feedback available is the one that is felt;
12319
+ * without it the hand waits, doubts the press was heard, and lets go too early. A
12320
+ * vibration is the usual answer, but nothing here is about vibration — a sound, a
12321
+ * class, a measure are the same moment.
12322
+ *
12323
+ * It is told, not asked: `grab` reports, so what it returns is not waited on and
12324
+ * preventing its event does not call the gesture off. And it is not an interaction
12325
+ * on its own — declared without one of the three above there is no gesture for it
12326
+ * to be the beginning of.
12327
+ *
12328
+ * A `longpress` needs nothing of this: it already happens at the moment the hold
12329
+ * is acquired rather than at the release (see interaction_press.js).
12330
+ *
12297
12331
  * What the copy LOOKS like is the application's too. A copy of a transparent
12298
12332
  * element is invisible — a row usually gets its background from the list around it,
12299
12333
  * which the copy has left — so it is dressed through the attributes the gesture
@@ -12304,6 +12338,8 @@ const swipeTypeOf = (axis, pulled) => {
12304
12338
  const MOVE = "move";
12305
12339
  const REORDER = "reorder";
12306
12340
  const TOSS = "toss";
12341
+ // The moment the press stops being a press and becomes a hold on the object.
12342
+ const GRAB = "grab";
12307
12343
 
12308
12344
  // What makes an element a place something can land, written by the detector itself.
12309
12345
  const REORDERABLE_ATTRIBUTE = "data-reorderable";
@@ -12319,11 +12355,16 @@ const TOSS_SPEED_ATTRIBUTE = "data-toss-speed";
12319
12355
 
12320
12356
  defineInteractionDetector({
12321
12357
  name: "drag",
12322
- claims: (type) => type === MOVE || type === REORDER || type === TOSS,
12358
+ claims: (type) =>
12359
+ type === MOVE || type === REORDER || type === TOSS || type === GRAB,
12323
12360
  setup: (element, trigger, { types, readConfig }) => {
12324
12361
  const canMove = types.includes(MOVE);
12325
12362
  const canReorder = types.includes(REORDER);
12326
12363
  const canToss = types.includes(TOSS);
12364
+ if (!canMove && !canReorder && !canToss) {
12365
+ return undefined;
12366
+ }
12367
+ const tellsWhenGrabbed = types.includes(GRAB);
12327
12368
  // Read at setup rather than at the press: a container can say it, and it is
12328
12369
  // what the gesture is about rather than something it discovers.
12329
12370
  const axisHolder = element.closest(`[${AXIS_ATTRIBUTE}]`);
@@ -12342,11 +12383,15 @@ defineInteractionDetector({
12342
12383
  // SURROUNDINGS scroll on, which for a list is the axis the list runs on.
12343
12384
  element.setAttribute("data-drag-source", axes === "x" ? "x" : "");
12344
12385
 
12386
+ // What a release can mean, which is not all of what was declared: "grab" is a
12387
+ // moment, not an outcome, and the gesture must not read it as one.
12388
+ const effects = types.filter((type) => type !== GRAB);
12389
+
12345
12390
  const onPointerDown = (pointerDownEvent) => {
12346
12391
  // What this element says a release can mean. The gesture then runs only what
12347
12392
  // those need — no copy for a move, no drop hint for something that can only
12348
12393
  // be thrown away.
12349
- startDragTo(pointerDownEvent, types, {
12394
+ startDragTo(pointerDownEvent, effects, {
12350
12395
  draggedElement: element,
12351
12396
  // Nothing to land on when nothing reorders.
12352
12397
  itemSelector: canReorder ? `[${REORDERABLE_ATTRIBUTE}]` : undefined,
@@ -12367,6 +12412,19 @@ defineInteractionDetector({
12367
12412
  longPressSlop: readConfig(SLOP_ATTRIBUTE, undefined),
12368
12413
  tossDistance: readConfig(TOSS_DISTANCE_ATTRIBUTE, undefined),
12369
12414
  tossSpeed: readConfig(TOSS_SPEED_ATTRIBUTE, undefined),
12415
+ // The one moment the gesture has that is not a release. Said here rather
12416
+ // than from the press that led to it, because the press is only one of the
12417
+ // two ways in: a finger holds still, a mouse travels a few pixels, and it
12418
+ // is the same instant — the object is now held. Nothing is done with what
12419
+ // comes back: this says something happened, it does not ask for work.
12420
+ onDragStart: tellsWhenGrabbed
12421
+ ? (gestureInfo) => {
12422
+ trigger(GRAB, pointerDownEvent, {
12423
+ pointerType: pointerDownEvent.pointerType,
12424
+ gestureInfo,
12425
+ });
12426
+ }
12427
+ : undefined,
12370
12428
  // Handed straight back in all three cases: what `trigger` returns is a
12371
12429
  // promise while the answer is still going, which is what the gesture waits
12372
12430
  // on before it lets go of what it carries.
@@ -25725,7 +25783,28 @@ installImportMetaCssBuild(import.meta);const css$W = /* css */`
25725
25783
  color: var(--x-button-color);
25726
25784
  background: none;
25727
25785
  border: none;
25728
- border-radius: var(--button-border-radius);
25786
+ /* Squared from the outside, corner by corner: a Group asks the member it
25787
+ joins for square corners along the seam, and the button it means may
25788
+ arrive wrapped (in a tooltip, in a link), so the ask travels down as
25789
+ inherited custom properties rather than as a selector aimed at the
25790
+ button. Each corner falls back to the button's own radius when nothing
25791
+ asks for anything. */
25792
+ border-top-left-radius: var(
25793
+ --x-corner-top-left-radius,
25794
+ var(--button-border-radius)
25795
+ );
25796
+ border-top-right-radius: var(
25797
+ --x-corner-top-right-radius,
25798
+ var(--button-border-radius)
25799
+ );
25800
+ border-bottom-right-radius: var(
25801
+ --x-corner-bottom-right-radius,
25802
+ var(--button-border-radius)
25803
+ );
25804
+ border-bottom-left-radius: var(
25805
+ --x-corner-bottom-left-radius,
25806
+ var(--button-border-radius)
25807
+ );
25729
25808
  outline: none;
25730
25809
  cursor: var(--x-button-cursor);
25731
25810
  touch-action: manipulation;
@@ -25733,6 +25812,14 @@ installImportMetaCssBuild(import.meta);const css$W = /* css */`
25733
25812
  -webkit-tap-highlight-color: var(--navi-control-tap-highlight-color);
25734
25813
 
25735
25814
  .navi_button_content {
25815
+ /* The ask stops here: this element is the button's frame, so what is
25816
+ inside it (a popover the button opens, a button of its own) is no
25817
+ longer at the seam. */
25818
+ --x-corner-top-left-radius: initial;
25819
+ --x-corner-top-right-radius: initial;
25820
+ --x-corner-bottom-right-radius: initial;
25821
+ --x-corner-bottom-left-radius: initial;
25822
+
25736
25823
  position: relative;
25737
25824
  display: inherit;
25738
25825
  box-sizing: border-box;
@@ -27745,6 +27832,15 @@ const css$V = /* css */`
27745
27832
  }
27746
27833
 
27747
27834
  .navi_dialog {
27835
+ /* A popup renders inside its opener's own subtree, so a corner claimed from
27836
+ the outside (see group.jsx) would otherwise reach the controls in here.
27837
+ It stops at the popup: what a popup holds is never at a seam of the group
27838
+ its opener belongs to. */
27839
+ --x-corner-top-left-radius: initial;
27840
+ --x-corner-top-right-radius: initial;
27841
+ --x-corner-bottom-right-radius: initial;
27842
+ --x-corner-bottom-left-radius: initial;
27843
+
27748
27844
  /* Computed once, reused by both max-width itself and min-width's own
27749
27845
  clamp below (see its comment for why) — avoids repeating the same
27750
27846
  min(..., ...) expression twice. */
@@ -29014,6 +29110,15 @@ const css$U = /* css */`
29014
29110
  }
29015
29111
 
29016
29112
  .navi_popover {
29113
+ /* A popup renders inside its opener's own subtree, so a corner claimed from
29114
+ the outside (see group.jsx) would otherwise reach the controls in here.
29115
+ It stops at the popup: what a popup holds is never at a seam of the group
29116
+ its opener belongs to. */
29117
+ --x-corner-top-left-radius: initial;
29118
+ --x-corner-top-right-radius: initial;
29119
+ --x-corner-bottom-right-radius: initial;
29120
+ --x-corner-bottom-left-radius: initial;
29121
+
29017
29122
  --x-popover-max-width: min(
29018
29123
  var(--popover-max-width, var(--popover-maxmax-width)),
29019
29124
  var(--container-position-remaining-width, var(--popover-maxmax-width)),
@@ -45669,6 +45774,14 @@ const inputCss = /* css */`
45669
45774
  }
45670
45775
 
45671
45776
  .navi_input_slot {
45777
+ /* A corner claimed from the outside (see group.jsx) is the frame's, and
45778
+ what lives in here is not the frame — a button in a slot, the content
45779
+ of a popup — so the ask stops at this boundary. */
45780
+ --x-corner-top-left-radius: initial;
45781
+ --x-corner-top-right-radius: initial;
45782
+ --x-corner-bottom-right-radius: initial;
45783
+ --x-corner-bottom-left-radius: initial;
45784
+
45672
45785
  margin-right: var(--slot-spacing, calc(2px + 0.1em));
45673
45786
  margin-left: var(--slot-spacing, calc(2px + 0.1em));
45674
45787
  color: #5e4e4e;
@@ -47099,11 +47212,19 @@ const css$B = /* css */`
47099
47212
  .navi_group {
47100
47213
  --group-border-width: var(--navi-control-border-width);
47101
47214
 
47102
- /* Squaring the joined corners is said on the direct child alone: a navi
47103
- control declares the radius of its frame on its own root, and whatever
47104
- inner element actually draws the frame inherits it. .navi_button_content
47105
- is the exception a button's frame sits on it and a button can arrive
47106
- wrapped (a tooltip, a link), so it is named on its own. */
47215
+ /* Two ways of asking for a square corner, and no selector ever reaching
47216
+ inside a member:
47217
+ - the property, on the member itself: a navi control declares the radius
47218
+ of its frame on its own root, and the inner element that draws that
47219
+ frame inherits it;
47220
+ - the custom property, which travels: a member can be an enrobage (a
47221
+ tooltip, a link) around the control that carries the frame, so the ask
47222
+ also goes down as --x-corner-*-radius, which a control reads as an
47223
+ override of its own radius. Private (the --x- prefix): navi's own
47224
+ controls answer it, an app never writes it. Whoever answers it also
47225
+ stops it (see .navi_button_content in button_ui.jsx), so a button
47226
+ deeper in — the clear cross in a picker's slot, the Save of a form in
47227
+ a popup the member opens — never mistakes itself for the seam. */
47107
47228
 
47108
47229
  /* Members overlap by the width of one border, so along each seam one of the
47109
47230
  two borders covers the other. Whichever member the user is on has to be
@@ -47121,8 +47242,15 @@ const css$B = /* css */`
47121
47242
  position: relative;
47122
47243
  z-index: var(--navi-z-index-control-hovered);
47123
47244
  }
47245
+ /* Three spellings for one thing — the member showing a focus ring. Some
47246
+ controls take the focus on their own root (a button); others wrap a real
47247
+ input and draw the ring on their frame while the keyboard is held
47248
+ somewhere inside (a picker, a spin). The ring is what must not be sliced,
47249
+ so the member holding it is raised whether it wears the state itself or
47250
+ merely contains it. */
47124
47251
  > *:focus-visible,
47125
- > *[data-focus-visible] {
47252
+ > *[data-focus-visible],
47253
+ > *:has([data-focus-visible]) {
47126
47254
  position: relative;
47127
47255
  z-index: var(--navi-z-index-control-focused);
47128
47256
  }
@@ -47133,31 +47261,28 @@ const css$B = /* css */`
47133
47261
  margin-left: calc(var(--border-width, var(--group-border-width)) * -1);
47134
47262
  }
47135
47263
  > *:first-child:not(:only-child) {
47264
+ --x-corner-top-right-radius: 0;
47265
+ --x-corner-bottom-right-radius: 0;
47266
+
47136
47267
  border-top-right-radius: 0 !important;
47137
47268
  border-bottom-right-radius: 0 !important;
47138
-
47139
- .navi_button_content {
47140
- border-top-right-radius: 0 !important;
47141
- border-bottom-right-radius: 0 !important;
47142
- }
47143
47269
  }
47144
47270
 
47145
47271
  > *:last-child:not(:only-child) {
47272
+ --x-corner-top-left-radius: 0;
47273
+ --x-corner-bottom-left-radius: 0;
47274
+
47146
47275
  border-top-left-radius: 0 !important;
47147
47276
  border-bottom-left-radius: 0 !important;
47148
-
47149
- .navi_button_content {
47150
- border-top-left-radius: 0 !important;
47151
- border-bottom-left-radius: 0 !important;
47152
- }
47153
47277
  }
47154
47278
 
47155
47279
  > *:not(:first-child):not(:last-child) {
47156
- border-radius: 0 !important;
47280
+ --x-corner-top-left-radius: 0;
47281
+ --x-corner-top-right-radius: 0;
47282
+ --x-corner-bottom-right-radius: 0;
47283
+ --x-corner-bottom-left-radius: 0;
47157
47284
 
47158
- .navi_button_content {
47159
- border-radius: 0 !important;
47160
- }
47285
+ border-radius: 0 !important;
47161
47286
  }
47162
47287
  }
47163
47288
 
@@ -47167,31 +47292,28 @@ const css$B = /* css */`
47167
47292
  margin-top: calc(var(--border-width, var(--group-border-width)) * -1);
47168
47293
  }
47169
47294
  > *:first-child:not(:only-child) {
47295
+ --x-corner-bottom-right-radius: 0;
47296
+ --x-corner-bottom-left-radius: 0;
47297
+
47170
47298
  border-bottom-right-radius: 0 !important;
47171
47299
  border-bottom-left-radius: 0 !important;
47172
-
47173
- .navi_button_content {
47174
- border-bottom-right-radius: 0 !important;
47175
- border-bottom-left-radius: 0 !important;
47176
- }
47177
47300
  }
47178
47301
 
47179
47302
  > *:last-child:not(:only-child) {
47303
+ --x-corner-top-left-radius: 0;
47304
+ --x-corner-top-right-radius: 0;
47305
+
47180
47306
  border-top-left-radius: 0 !important;
47181
47307
  border-top-right-radius: 0 !important;
47182
-
47183
- .navi_button_content {
47184
- border-top-left-radius: 0 !important;
47185
- border-top-right-radius: 0 !important;
47186
- }
47187
47308
  }
47188
47309
 
47189
47310
  > *:not(:first-child):not(:last-child) {
47190
- border-radius: 0 !important;
47311
+ --x-corner-top-left-radius: 0;
47312
+ --x-corner-top-right-radius: 0;
47313
+ --x-corner-bottom-right-radius: 0;
47314
+ --x-corner-bottom-left-radius: 0;
47191
47315
 
47192
- .navi_button_content {
47193
- border-radius: 0 !important;
47194
- }
47316
+ border-radius: 0 !important;
47195
47317
  }
47196
47318
  }
47197
47319
  }
@@ -47205,8 +47327,15 @@ const Group = ({
47205
47327
  import.meta.css = [css$B, "@jsenv/navi/src/control/group.jsx"];
47206
47328
  return jsx(Box, {
47207
47329
  baseClassName: "navi_group",
47208
- "data-vertical": vertical ? "" : undefined,
47209
- row: row,
47330
+ "data-vertical": vertical ? "" : undefined
47331
+ /* A group draws one frame around its members, so they must share their
47332
+ top and bottom edges. A block box lays them out inline, where they
47333
+ align on baselines instead: a member whose content carries no text —
47334
+ the swatch of a color picker — has no baseline of its own, the one
47335
+ synthesized from its bottom edge does not land where its neighbours'
47336
+ text does, and the member rides a few pixels off, slicing the frame.
47337
+ Flex aligns the edges, in both directions. */,
47338
+ flex: vertical ? "y" : "x",
47210
47339
  ...props,
47211
47340
  children: children
47212
47341
  });
@@ -57431,6 +57560,14 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
57431
57560
  }
57432
57561
  }
57433
57562
  .navi_picker_right_slot {
57563
+ /* A corner claimed from the outside (see group.jsx) is the frame's, and
57564
+ what lives in here is not the frame — a button in a slot, the content
57565
+ of a popup — so the ask stops at this boundary. */
57566
+ --x-corner-top-left-radius: initial;
57567
+ --x-corner-top-right-radius: initial;
57568
+ --x-corner-bottom-right-radius: initial;
57569
+ --x-corner-bottom-left-radius: initial;
57570
+
57434
57571
  display: inline-flex;
57435
57572
  height: 1em;
57436
57573
  height: 1lh;