@planningcenter/topbar 12.5.0-rc.3 → 12.5.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.
@@ -3346,6 +3346,25 @@ true;
3346
3346
  }));
3347
3347
  }
3348
3348
 
3349
+ // With the search's own debounce, about 1.4 seconds after the last keystroke,
3350
+ // as in GOV.UK's accessible-autocomplete.
3351
+ const ANNOUNCEMENT_DELAY_MS = 1100;
3352
+ // Screen readers echo typing themselves, as each person has set them to, so
3353
+ // the message waits until typing settles instead of talking over it. Waiting
3354
+ // also lets VoiceOver finish what focus had to say, since it often skips a
3355
+ // change made as focus moves, as when a tab with cached results is selected,
3356
+ // and only the tab someone stops on when arrowing through them is announced.
3357
+ // The region empties at once, so a message that comes back is read again.
3358
+ function useAnnouncement(message) {
3359
+ const [announcement, setAnnouncement] = React.useState(null);
3360
+ React.useEffect(() => {
3361
+ setAnnouncement(null);
3362
+ const timeout = setTimeout(() => setAnnouncement(message), ANNOUNCEMENT_DELAY_MS);
3363
+ return () => clearTimeout(timeout);
3364
+ }, [message]);
3365
+ return announcement;
3366
+ }
3367
+
3349
3368
  // useId isn't available on React 17, which our peer range still supports.
3350
3369
  let instanceCount$1 = 0;
3351
3370
  function useInstanceId(prefix) {
@@ -4095,7 +4114,18 @@ true;
4095
4114
  return undefined;
4096
4115
  }
4097
4116
  }
4098
- async function mintToken(personId) {
4117
+ const minting = new Map();
4118
+ function mintToken(personId) {
4119
+ const running = minting.get(personId);
4120
+ if (running) {
4121
+ return running;
4122
+ } else {
4123
+ const run = mint(personId).finally(() => minting.delete(personId));
4124
+ minting.set(personId, run);
4125
+ return run;
4126
+ }
4127
+ }
4128
+ async function mint(personId) {
4099
4129
  const {
4100
4130
  json,
4101
4131
  ok,
@@ -4119,6 +4149,10 @@ true;
4119
4149
  }
4120
4150
  return token;
4121
4151
  }
4152
+ function currentToken(personId) {
4153
+ var _a;
4154
+ return (_a = cachedToken(personId)) !== null && _a !== void 0 ? _a : mintToken(personId).catch(() => null);
4155
+ }
4122
4156
  async function request(personId, key, init = {}, isRetry = false) {
4123
4157
  var _a;
4124
4158
  const token = isRetry ? await mintToken(personId) : (_a = cachedToken(personId)) !== null && _a !== void 0 ? _a : await mintToken(personId);
@@ -4154,8 +4188,8 @@ true;
4154
4188
  // retrying when another tab or device wrote in between. `change` returns null
4155
4189
  // when there's nothing to write. A read-only token (staff impersonating
4156
4190
  // someone) only reads, so nothing is written into the person's history.
4157
- // Resolves to the value now stored, or null when the service couldn't be
4158
- // reached.
4191
+ // Resolves to the value now stored, with whether the token was read-only, or
4192
+ // null when the service couldn't be reached.
4159
4193
  async function updateStoredValue(personId, key, change, attemptsLeft = MAX_ATTEMPTS) {
4160
4194
  const read = await request(personId, key);
4161
4195
  if (read === null || read === void 0 ? void 0 : read.response.ok) {
@@ -4163,9 +4197,15 @@ true;
4163
4197
  value,
4164
4198
  version
4165
4199
  } = await read.response.json();
4166
- const next = read.token.readOnly ? null : change(value);
4200
+ const {
4201
+ readOnly
4202
+ } = read.token;
4203
+ const next = readOnly ? null : change(value);
4167
4204
  if (next === null) {
4168
- return Array.isArray(value) ? value : [];
4205
+ return {
4206
+ readOnly,
4207
+ value: Array.isArray(value) ? value : []
4208
+ };
4169
4209
  } else {
4170
4210
  const write = await request(personId, key, {
4171
4211
  body: JSON.stringify({
@@ -4178,7 +4218,10 @@ true;
4178
4218
  method: "PUT"
4179
4219
  });
4180
4220
  if (write === null || write === void 0 ? void 0 : write.response.ok) {
4181
- return next;
4221
+ return {
4222
+ readOnly,
4223
+ value: next
4224
+ };
4182
4225
  } else if ((write === null || write === void 0 ? void 0 : write.response.status) === 409 && attemptsLeft > 1) {
4183
4226
  return updateStoredValue(personId, key, change, attemptsLeft - 1);
4184
4227
  } else {
@@ -4230,7 +4273,8 @@ true;
4230
4273
  return isSameList(mergeEntries(shared, local), shared) ? null : mergeStored(stored, local);
4231
4274
  });
4232
4275
  if (synced) {
4233
- writeRecentlyVisited(scope, mergeEntries(entriesFrom(synced), readRecentlyVisited(scope)));
4276
+ const shared = entriesFrom(synced.value);
4277
+ writeRecentlyVisited(scope, synced.readOnly ? shared : mergeEntries(shared, readRecentlyVisited(scope)));
4234
4278
  }
4235
4279
  } catch {
4236
4280
  // The service is best-effort; the local copy stands on its own.
@@ -4261,11 +4305,23 @@ true;
4261
4305
  function syncRecentlyVisited(scope) {
4262
4306
  return sync(scope, false);
4263
4307
  }
4308
+ function record(entry, scope, token) {
4309
+ if (!(token === null || token === void 0 ? void 0 : token.readOnly)) {
4310
+ writeRecentlyVisited(scope, addVisit(readRecentlyVisited(scope), entry));
4311
+ if (token) sync(scope, true);
4312
+ }
4313
+ }
4264
4314
  function recordVisit(visit, scope) {
4265
4315
  const entry = toEntry(visit, new Date());
4266
- if (isEntry(entry)) {
4267
- writeRecentlyVisited(scope, addVisit(readRecentlyVisited(scope), entry));
4268
- sync(scope, true);
4316
+ if (!isEntry(entry)) {
4317
+ return Promise.resolve();
4318
+ }
4319
+ const token = isUserStoreSyncUnavailable() ? null : currentToken(scope.personId);
4320
+ if (token instanceof Promise) {
4321
+ return token.then(minted => record(entry, scope, minted));
4322
+ } else {
4323
+ record(entry, scope, token);
4324
+ return Promise.resolve();
4269
4325
  }
4270
4326
  }
4271
4327
  function useRecentlyVisited({
@@ -4946,7 +5002,6 @@ true;
4946
5002
  role: "tablist"
4947
5003
  }, products.map(product => (/*#__PURE__*/React.createElement(Tab, {
4948
5004
  "aria-controls": `${id}-panel`,
4949
- "aria-describedby": `${tabId(product.slug)}-description`,
4950
5005
  "aria-selected": product === selected,
4951
5006
  id: tabId(product.slug),
4952
5007
  key: product.slug,
@@ -4964,11 +5019,6 @@ true;
4964
5019
  }), showAllNames || product === selected ? product.appName : (/*#__PURE__*/React.createElement("span", {
4965
5020
  className: styles$k.visuallyHidden
4966
5021
  }, product.appName)))))), /*#__PURE__*/React.createElement("div", {
4967
- className: styles$k.visuallyHidden
4968
- }, products.map(product => (/*#__PURE__*/React.createElement("span", {
4969
- id: `${tabId(product.slug)}-description`,
4970
- key: product.slug
4971
- }, "Planning Center ", product.appName)))), /*#__PURE__*/React.createElement("div", {
4972
5022
  "aria-labelledby": tabId(selected.slug),
4973
5023
  className: styles$k.panel,
4974
5024
  id: `${id}-panel`,
@@ -6063,9 +6113,9 @@ true;
6063
6113
  });
6064
6114
  case "done":
6065
6115
  if (sections.length === 0) {
6116
+ // The panel's result count announces this, so it isn't read twice.
6066
6117
  return /*#__PURE__*/React.createElement(BlankState$1, {
6067
6118
  image: /*#__PURE__*/React.createElement(BlankStateNoResults, null),
6068
- role: "status",
6069
6119
  subtitle: "Clear search and try again.",
6070
6120
  title: `No results for “${term.trim()}”`
6071
6121
  });
@@ -6119,6 +6169,7 @@ true;
6119
6169
  highlightedIndex
6120
6170
  } = navigation;
6121
6171
  const inputRef = React.useRef(null);
6172
+ const resultCount = useAnnouncement(hasTerm && search.status === "done" ? `${items.length === 0 ? "No results" : `${items.length} ${items.length === 1 ? "result" : "results"}`} for “${term.trim()}” in ${product.appName}` : null);
6122
6173
  const [announceHighlight, setAnnounceHighlight] = React.useState(false);
6123
6174
  // The tabs keep focus while arrowing through results, but only the focused
6124
6175
  // input can point screen readers at the highlight, so from the tabs it's
@@ -6177,7 +6228,7 @@ true;
6177
6228
  "aria-live": "polite",
6178
6229
  className: styles$k.visuallyHidden,
6179
6230
  "data-testid": "result-count"
6180
- }, hasTerm && search.status === "done" && items.length > 0 ? `${items.length} ${items.length === 1 ? "result" : "results"} for “${term.trim()}” in ${product.appName}` : null), mobile ? null : /*#__PURE__*/React.createElement(KeyboardHints, {
6231
+ }, resultCount), mobile ? null : /*#__PURE__*/React.createElement(KeyboardHints, {
6181
6232
  copied: navigation.copied
6182
6233
  }));
6183
6234
  }