@planningcenter/topbar 11.2.5 → 12.0.0-qa-832.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,8 +1,8 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@planningcenter/tapestry'), require('react'), require('@planningcenter/url'), require('@planningcenter/icons/paths/general'), require('@planningcenter/jolt-client'), require('use-sync-external-store/shim'), require('@planningcenter/tapestry-react'), require('@planningcenter/add-ons'), require('@planningcenter/icons/paths/church-center'), require('@planningcenter/icons/paths/people'), require('@planningcenter/icons/paths/toolbar'), require('react-calendly'), require('algoliasearch'), require('react-instantsearch-core'), require('react-transition-group'), require('@planningcenter/datetime-fmt')) :
3
- typeof define === 'function' && define.amd ? define(['exports', '@planningcenter/tapestry', 'react', '@planningcenter/url', '@planningcenter/icons/paths/general', '@planningcenter/jolt-client', 'use-sync-external-store/shim', '@planningcenter/tapestry-react', '@planningcenter/add-ons', '@planningcenter/icons/paths/church-center', '@planningcenter/icons/paths/people', '@planningcenter/icons/paths/toolbar', 'react-calendly', 'algoliasearch', 'react-instantsearch-core', 'react-transition-group', '@planningcenter/datetime-fmt'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.PLANNINGCENTER = global.PLANNINGCENTER || {}, global.PLANNINGCENTER.Topbar = {}), global.tapestry, global.react, global.pcoUrl, global.general, global.Jolt, global.shim, global.tapestryReact, global.addOns, global.churchCenter, global.people, global.toolbar, global.reactCalendly, global.algoliasearch, global.reactInstantsearchCore, global.reactTransitionGroup, global.datetimeFmt));
5
- })(this, (function (exports, tapestry, React, pcoUrl$1, general, Jolt, shim, tapestryReact, addOns, churchCenter, people, toolbar, reactCalendly, algoliasearch, reactInstantsearchCore, reactTransitionGroup, datetimeFmt) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@planningcenter/tapestry'), require('react'), require('@planningcenter/url'), require('@planningcenter/icons/paths/general'), require('@planningcenter/jolt-client'), require('use-sync-external-store/shim'), require('react/jsx-runtime'), require('@planningcenter/tapestry-react'), require('@planningcenter/add-ons'), require('@planningcenter/icons/paths/church-center'), require('@planningcenter/icons/paths/people'), require('@planningcenter/icons/paths/toolbar'), require('react-calendly'), require('algoliasearch'), require('react-instantsearch-core'), require('react-transition-group'), require('@planningcenter/datetime-fmt')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', '@planningcenter/tapestry', 'react', '@planningcenter/url', '@planningcenter/icons/paths/general', '@planningcenter/jolt-client', 'use-sync-external-store/shim', 'react/jsx-runtime', '@planningcenter/tapestry-react', '@planningcenter/add-ons', '@planningcenter/icons/paths/church-center', '@planningcenter/icons/paths/people', '@planningcenter/icons/paths/toolbar', 'react-calendly', 'algoliasearch', 'react-instantsearch-core', 'react-transition-group', '@planningcenter/datetime-fmt'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.PLANNINGCENTER = global.PLANNINGCENTER || {}, global.PLANNINGCENTER.Topbar = {}), global.tapestry, global.react, global.pcoUrl, global.general, global.Jolt, global.shim, null, global.tapestryReact, global.addOns, global.churchCenter, global.people, global.toolbar, global.reactCalendly, global.algoliasearch, global.reactInstantsearchCore, global.reactTransitionGroup, global.datetimeFmt));
5
+ })(this, (function (exports, tapestry, React, pcoUrl$1, general, Jolt, shim, jsxRuntime, tapestryReact, addOns, churchCenter, people, toolbar, reactCalendly, algoliasearch, reactInstantsearchCore, reactTransitionGroup, datetimeFmt) { 'use strict';
6
6
 
7
7
  function _interopNamespaceDefault(e) {
8
8
  var n = Object.create(null);
@@ -951,8 +951,8 @@
951
951
  const notificationsEnabled = !window.notificationsDisabled;
952
952
  const organizationTimeZone = ((_k = (_j = topbarResponse === null || topbarResponse === void 0 ? void 0 : topbarResponse.data) === null || _j === void 0 ? void 0 : _j.attributes) === null || _k === void 0 ? void 0 : _k.org_time_zone) || "America/Los_Angeles";
953
953
  const algoliaSearchAuth = {
954
- algoliaApplicationId: (_m = (_l = topbarResponse === null || topbarResponse === void 0 ? void 0 : topbarResponse.data) === null || _l === void 0 ? void 0 : _l.attributes) === null || _m === void 0 ? void 0 : _m.algolia_application_identifier,
955
- algoliaSearchOnlyApiKey: (_p = (_o = topbarResponse === null || topbarResponse === void 0 ? void 0 : topbarResponse.data) === null || _o === void 0 ? void 0 : _o.attributes) === null || _p === void 0 ? void 0 : _p.algolia_search_key
954
+ algoliaApplicationId: (_m = (_l = topbarResponse === null || topbarResponse === void 0 ? void 0 : topbarResponse.data) === null || _l === void 0 ? void 0 : _l.attributes) === null || _m === void 0 ? void 0 : _m.algolia_help_center_application_identifier,
955
+ algoliaSearchOnlyApiKey: (_p = (_o = topbarResponse === null || topbarResponse === void 0 ? void 0 : topbarResponse.data) === null || _o === void 0 ? void 0 : _o.attributes) === null || _p === void 0 ? void 0 : _p.algolia_help_center_search_key
956
956
  };
957
957
  const joltPromise = React.useMemo(() => {
958
958
  return resolveJoltPromise({
@@ -2235,10 +2235,750 @@
2235
2235
  const appsMenuFormatter = apps => sortByAttribute("name")(apps).reduce((acc, item) => (item.name || "") === "Church Center" || (item.name || "") === "API" ? acc : item.name === "Home" ? [item, ...acc] : [...acc, item], []);
2236
2236
  const connectedPeopleMenuFormatter = connectedPeople => connectedPeople.slice().sort((a, b) => a.attributes["organization_name"].localeCompare(b.attributes["organization_name"]));
2237
2237
 
2238
+ // Generated by scripts/write-version.cjs from package.json. Do not edit.
2239
+ const BRIDGE_VERSION = "0.0.7-qa-41.0";
2240
+
2241
+ /**
2242
+ * PCA_BRIDGE_BUS_INIT — the script injected via
2243
+ * `injectedJavaScriptBeforeContentLoaded` before any web content loads.
2244
+ *
2245
+ * Installs `window.__pcaBridge` in the WebView's JS context. The bus:
2246
+ * - is per-WebView-context-by-construction (each WebView has its own JS
2247
+ * context; the global lives only in that context)
2248
+ * - accepts multiple registered handlers per reserved message type so
2249
+ * multiple Bridge instances in the same WebView can coexist
2250
+ * - buffers messages of a type with no registered handlers for up to 5s
2251
+ * - pings native with `__bridge/busReady` after install so the native
2252
+ * transport can flush its outgoing queue and trigger `reannounceLocal()`
2253
+ *
2254
+ * The bus runs as an IIFE so consumers can dispatch and register against it
2255
+ * immediately. Internal state (buffers, handlers) is closure-private.
2256
+ */
2257
+
2258
+ const PCA_BRIDGE_BUS_INIT = `
2259
+ (function () {
2260
+ if (typeof window === 'undefined') return
2261
+ if (window.__pcaBridge) return // idempotent in case it's injected twice
2262
+
2263
+ var BRIDGE_VERSION = ${JSON.stringify(BRIDGE_VERSION)}
2264
+ var BUFFER_TTL_MS = 5000
2265
+ var handlers = Object.create(null) // type -> Array<fn>
2266
+ var buffer = Object.create(null) // type -> Array<{msg, receivedAt}>
2267
+ var logger = {
2268
+ debug: function () {},
2269
+ warn: function (msg, ctx) {
2270
+ try { console.warn('[pca-bridge]', msg, ctx) } catch (e) {}
2271
+ },
2272
+ error: function (msg, ctx) {
2273
+ try { console.error('[pca-bridge]', msg, ctx) } catch (e) {}
2274
+ }
2275
+ }
2276
+
2277
+ function now() { return Date.now() }
2278
+
2279
+ function sweepBuffer() {
2280
+ var cutoff = now() - BUFFER_TTL_MS
2281
+ var dropped = []
2282
+ var hasAny = false
2283
+ for (var type in buffer) {
2284
+ var entries = buffer[type]
2285
+ var kept = []
2286
+ for (var i = 0; i < entries.length; i++) {
2287
+ if (entries[i].receivedAt >= cutoff) {
2288
+ kept.push(entries[i])
2289
+ } else {
2290
+ dropped.push({ type: type, ageMs: now() - entries[i].receivedAt })
2291
+ }
2292
+ }
2293
+ if (kept.length === 0) delete buffer[type]
2294
+ else {
2295
+ buffer[type] = kept
2296
+ hasAny = true
2297
+ }
2298
+ }
2299
+ if (dropped.length > 0 && window.ReactNativeWebView && typeof window.ReactNativeWebView.postMessage === 'function') {
2300
+ try {
2301
+ window.ReactNativeWebView.postMessage(JSON.stringify({
2302
+ type: '__bridge/drop',
2303
+ bridgeVersion: BRIDGE_VERSION,
2304
+ messages: dropped
2305
+ }))
2306
+ } catch (e) {}
2307
+ }
2308
+ // Stop ticking when the buffer drains; ensureSweep() restarts on demand.
2309
+ if (!hasAny && sweepInterval !== null) {
2310
+ clearInterval(sweepInterval)
2311
+ sweepInterval = null
2312
+ }
2313
+ }
2314
+
2315
+ var sweepInterval = null
2316
+ function ensureSweep() {
2317
+ if (sweepInterval !== null) return
2318
+ sweepInterval = setInterval(sweepBuffer, 1000)
2319
+ }
2320
+
2321
+ function register(type, fn) {
2322
+ if (!handlers[type]) handlers[type] = []
2323
+ handlers[type].push(fn)
2324
+ // Drain any buffered messages for this type, applying the same TTL the
2325
+ // background sweep uses so entries that already expired don't get
2326
+ // delivered just because sweep hasn't run yet.
2327
+ var pending = buffer[type]
2328
+ if (pending && pending.length > 0) {
2329
+ delete buffer[type]
2330
+ var cutoff = now() - BUFFER_TTL_MS
2331
+ for (var i = 0; i < pending.length; i++) {
2332
+ if (pending[i].receivedAt < cutoff) continue
2333
+ try { fn(pending[i].msg) } catch (e) {
2334
+ logger.error('Handler threw while draining buffer', { type: type, error: e })
2335
+ }
2336
+ }
2337
+ }
2338
+ }
2339
+
2340
+ function unregister(type, fn) {
2341
+ var arr = handlers[type]
2342
+ if (!arr) return
2343
+ var idx = arr.indexOf(fn)
2344
+ if (idx >= 0) arr.splice(idx, 1)
2345
+ if (arr.length === 0) delete handlers[type]
2346
+ }
2347
+
2348
+ function dispatch(msg) {
2349
+ if (!msg || typeof msg !== 'object' || typeof msg.type !== 'string') return
2350
+ var type = msg.type
2351
+ var arr = handlers[type]
2352
+ if (arr && arr.length > 0) {
2353
+ // Snapshot the array to avoid issues if a handler registers/unregisters during dispatch
2354
+ var snapshot = arr.slice()
2355
+ for (var i = 0; i < snapshot.length; i++) {
2356
+ try { snapshot[i](msg) } catch (e) {
2357
+ logger.error('Handler threw during dispatch', { type: type, error: e })
2358
+ }
2359
+ }
2360
+ } else {
2361
+ if (!buffer[type]) buffer[type] = []
2362
+ buffer[type].push({ msg: msg, receivedAt: now() })
2363
+ ensureSweep()
2364
+ }
2365
+ }
2366
+
2367
+ function setLogger(next) {
2368
+ // Validate both methods the bus actually calls; a partial logger would
2369
+ // throw TypeError when dispatch/register tries to invoke a missing one.
2370
+ if (next && typeof next.warn === 'function' && typeof next.error === 'function') {
2371
+ logger = next
2372
+ }
2373
+ }
2374
+
2375
+ window.__pcaBridge = {
2376
+ register: register,
2377
+ unregister: unregister,
2378
+ dispatch: dispatch,
2379
+ setLogger: setLogger
2380
+ }
2381
+
2382
+ // Ping native: the bus is ready in this JS context. Native transport
2383
+ // listens for this to (a) flush its outgoing queue and (b) call
2384
+ // bridge.reannounceLocal() so the freshly-loaded web context learns about
2385
+ // already-exposed local actions/values.
2386
+ try {
2387
+ if (window.ReactNativeWebView && typeof window.ReactNativeWebView.postMessage === 'function') {
2388
+ window.ReactNativeWebView.postMessage(JSON.stringify({ type: '__bridge/busReady', bridgeVersion: BRIDGE_VERSION }))
2389
+ }
2390
+ } catch (e) {}
2391
+ })();
2392
+ true;
2393
+ `;
2394
+
2395
+ class TimeoutError extends Error {
2396
+ constructor(name, timeoutMs) {
2397
+ super(`Call to "${name}" timed out after ${timeoutMs}ms`);
2398
+ this.name = 'TimeoutError';
2399
+ }
2400
+ }
2401
+ class HandlerError extends Error {
2402
+ constructor(name, code, originalMessage) {
2403
+ super(`Handler for "${name}" threw [${code}]: ${originalMessage}`);
2404
+ this.name = 'HandlerError';
2405
+ }
2406
+ }
2407
+ class DisposedError extends Error {
2408
+ constructor(name) {
2409
+ super(`Bridge disposed while call to "${name}" was in flight`);
2410
+ this.name = 'DisposedError';
2411
+ }
2412
+ }
2413
+
2414
+ let idCounter = 0;
2415
+ function generateId() {
2416
+ idCounter += 1;
2417
+ return `pca-bridge-${idCounter}`;
2418
+ }
2419
+ const noopLogger = {
2420
+ debug: () => {},
2421
+ warn: () => {},
2422
+ error: () => {}
2423
+ };
2424
+ function isIndexable(x) {
2425
+ return typeof x === 'object' && x !== null;
2426
+ }
2427
+
2428
+ /**
2429
+ * Shallow equality, one level deep. Used by `BridgeCore.publish` to dedup
2430
+ * re-emissions of new-but-content-equal values (e.g., a freshly-built
2431
+ * `modalButtons` array whose contents didn't change between renders).
2432
+ */
2433
+ function shallowEqual(a, b) {
2434
+ const sameRef = Object.is(a, b);
2435
+ const bothComparableObjects = isIndexable(a) && isIndexable(b) && Array.isArray(a) === Array.isArray(b);
2436
+ let result;
2437
+ if (sameRef) {
2438
+ result = true;
2439
+ } else if (!bothComparableObjects) {
2440
+ result = false;
2441
+ } else {
2442
+ const keys = Object.keys(a);
2443
+ result = keys.length === Object.keys(b).length && keys.every(k => Object.prototype.hasOwnProperty.call(b, k) && Object.is(a[k], b[k]));
2444
+ }
2445
+ return result;
2446
+ }
2447
+ function isWireEnvelope(parsed) {
2448
+ return typeof parsed === 'object' && parsed !== null && 'type' in parsed && typeof parsed.type === 'string' && parsed.type.startsWith('__bridge/') && 'bridgeVersion' in parsed && typeof parsed.bridgeVersion === 'string';
2449
+ }
2450
+ function parseWireEnvelope(rawData) {
2451
+ let parsed;
2452
+ try {
2453
+ parsed = JSON.parse(rawData);
2454
+ } catch {
2455
+ parsed = null;
2456
+ }
2457
+ return isWireEnvelope(parsed) ? parsed : null;
2458
+ }
2459
+
2460
+ const DEFAULT_TIMEOUT_MS = 5000;
2461
+ const INCOMING_CALL_QUEUE_TTL_MS = 5000;
2462
+ const INCOMING_CALL_QUEUE_MAX_PER_ACTION = 32;
2463
+ class BridgeCore {
2464
+ injectedJavaScriptBeforeContentLoaded = PCA_BRIDGE_BUS_INIT;
2465
+ localVersion = BRIDGE_VERSION;
2466
+ // `never` arg type lets us widen any user-defined `LocalActions[K]` callable
2467
+ // with a single `as` cast inside `expose()`.
2468
+ localActions = new Map();
2469
+ localValues = new Map();
2470
+ remoteActions = new Set();
2471
+ remoteValues = new Map();
2472
+ actionListeners = new Map();
2473
+ valueListeners = new Map();
2474
+ pendingCalls = new Map();
2475
+ callQueue = new Map();
2476
+
2477
+ // Private backing field + public getter so the `readonly` declared on the
2478
+ // Bridge interface is actually enforced — not just at the interface level
2479
+ // but for any code holding a concrete BridgeCore reference.
2480
+ _remoteVersion = undefined;
2481
+ get remoteVersion() {
2482
+ return this._remoteVersion;
2483
+ }
2484
+ disposed = false;
2485
+ constructor(options) {
2486
+ const sendRaw = options.send;
2487
+ this.send = msg => {
2488
+ sendRaw({
2489
+ ...msg,
2490
+ bridgeVersion: BRIDGE_VERSION
2491
+ });
2492
+ };
2493
+ this.logger = options.logger ?? noopLogger;
2494
+ this.defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
2495
+ this.now = options.now ?? Date.now;
2496
+ }
2497
+ call(name, args, options) {
2498
+ const requestId = generateId();
2499
+ const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
2500
+ return new Promise((resolve, reject) => {
2501
+ const timer = setTimeout(() => {
2502
+ if (this.pendingCalls.delete(requestId)) {
2503
+ reject(new TimeoutError(name, timeoutMs));
2504
+ }
2505
+ }, timeoutMs);
2506
+ // Heterogeneous map: cast the typed resolve to the loose storage slot.
2507
+ this.pendingCalls.set(requestId, {
2508
+ name,
2509
+ resolve: resolve,
2510
+ reject,
2511
+ timer
2512
+ });
2513
+ this.send({
2514
+ type: '__bridge/call',
2515
+ requestId,
2516
+ name,
2517
+ args
2518
+ });
2519
+ });
2520
+ }
2521
+ expose(name, handler) {
2522
+ if (this.localActions.has(name)) {
2523
+ this.logger.warn('Action already exposed; last-write-wins (the previous handler is gone)', {
2524
+ name
2525
+ });
2526
+ }
2527
+ this.localActions.set(name, handler);
2528
+ this.send({
2529
+ type: '__bridge/exposed',
2530
+ name,
2531
+ kind: 'action'
2532
+ });
2533
+ const queued = this.callQueue.get(name);
2534
+ if (queued) {
2535
+ this.callQueue.delete(name);
2536
+ const cutoff = this.now() - INCOMING_CALL_QUEUE_TTL_MS;
2537
+ for (const entry of queued) {
2538
+ if (entry.receivedAt >= cutoff) {
2539
+ void this.invokeLocalHandler(entry.msg);
2540
+ } else {
2541
+ this.logger.warn('Queued incoming call expired before handler registered', {
2542
+ name,
2543
+ ageMs: this.now() - entry.receivedAt
2544
+ });
2545
+ this.rejectQueued(entry, 'expired');
2546
+ }
2547
+ }
2548
+ }
2549
+ }
2550
+ unexpose(name) {
2551
+ if (this.localActions.delete(name)) {
2552
+ this.send({
2553
+ type: '__bridge/unexposed',
2554
+ name,
2555
+ kind: 'action'
2556
+ });
2557
+ }
2558
+ }
2559
+ hasRemoteAction(name) {
2560
+ return this.remoteActions.has(name);
2561
+ }
2562
+ onRemoteAction(name, callback) {
2563
+ let set = this.actionListeners.get(name);
2564
+ if (!set) {
2565
+ set = new Set();
2566
+ this.actionListeners.set(name, set);
2567
+ }
2568
+ set.add(callback);
2569
+ return () => {
2570
+ const current = this.actionListeners.get(name);
2571
+ if (current) {
2572
+ current.delete(callback);
2573
+ if (current.size === 0) {
2574
+ this.actionListeners.delete(name);
2575
+ }
2576
+ }
2577
+ };
2578
+ }
2579
+ publish(name, value) {
2580
+ // Skip wire send if content unchanged — callers may publish on every
2581
+ // render with a fresh-but-content-equal value.
2582
+ if (!shallowEqual(this.localValues.get(name), value)) {
2583
+ this.localValues.set(name, value);
2584
+ this.send({
2585
+ type: '__bridge/publish',
2586
+ name,
2587
+ value
2588
+ });
2589
+ }
2590
+ }
2591
+ unpublish(name) {
2592
+ if (this.localValues.delete(name)) {
2593
+ this.send({
2594
+ type: '__bridge/unexposed',
2595
+ name,
2596
+ kind: 'value'
2597
+ });
2598
+ }
2599
+ }
2600
+ seedLocalValue(name, value) {
2601
+ this.localValues.set(name, value);
2602
+ }
2603
+ getRemoteValue(name) {
2604
+ return this.remoteValues.get(name);
2605
+ }
2606
+ onRemoteValue(name, callback) {
2607
+ let set = this.valueListeners.get(name);
2608
+ if (!set) {
2609
+ set = new Set();
2610
+ this.valueListeners.set(name, set);
2611
+ }
2612
+ const wrapped = callback;
2613
+ set.add(wrapped);
2614
+ return () => {
2615
+ const current = this.valueListeners.get(name);
2616
+ if (current) {
2617
+ current.delete(wrapped);
2618
+ if (current.size === 0) {
2619
+ this.valueListeners.delete(name);
2620
+ }
2621
+ }
2622
+ };
2623
+ }
2624
+ reannounceLocal() {
2625
+ for (const name of this.localActions.keys()) {
2626
+ this.send({
2627
+ type: '__bridge/exposed',
2628
+ name,
2629
+ kind: 'action'
2630
+ });
2631
+ }
2632
+ for (const [name, value] of this.localValues) {
2633
+ this.send({
2634
+ type: '__bridge/exposed',
2635
+ name,
2636
+ kind: 'value',
2637
+ currentValue: value
2638
+ });
2639
+ }
2640
+ }
2641
+
2642
+ /**
2643
+ * Drop remote-side cached state. Called by transports when the remote JS
2644
+ * context is about to be torn down (e.g., WebView reload): the new context
2645
+ * will re-announce what it actually exposes, and stale entries would
2646
+ * otherwise silently mask removed actions/values.
2647
+ */
2648
+ clearRemoteState() {
2649
+ for (const name of this.remoteActions) {
2650
+ this.notifySet(this.actionListeners, name, false, 'action availability');
2651
+ }
2652
+ this.remoteActions.clear();
2653
+ for (const name of this.remoteValues.keys()) {
2654
+ this.notifySet(this.valueListeners, name, undefined, 'remote value change');
2655
+ }
2656
+ this.remoteValues.clear();
2657
+ this._remoteVersion = undefined;
2658
+ }
2659
+ handleMessage(rawData) {
2660
+ const msg = parseWireEnvelope(rawData);
2661
+ let consumed = false;
2662
+ if (msg) {
2663
+ this.handleIncomingMessage(msg);
2664
+ consumed = true;
2665
+ }
2666
+ return consumed;
2667
+ }
2668
+ applyLogger(logger) {
2669
+ if (this.logger === noopLogger) {
2670
+ this.logger = logger;
2671
+ return true;
2672
+ }
2673
+ return false;
2674
+ }
2675
+ handleLoadStart() {}
2676
+ handleLoadEnd() {}
2677
+ handleNavigationStateChange(navState) {
2678
+ if (!navState.loading) {
2679
+ this.handleLoadEnd();
2680
+ }
2681
+ }
2682
+ handleIncomingMessage(msg) {
2683
+ if (!this.disposed) {
2684
+ this._remoteVersion = msg.bridgeVersion;
2685
+ switch (msg.type) {
2686
+ case '__bridge/call':
2687
+ void this.routeIncomingCall(msg);
2688
+ break;
2689
+ case '__bridge/response':
2690
+ this.handleIncomingResponse(msg);
2691
+ break;
2692
+ case '__bridge/exposed':
2693
+ this.handleIncomingExposed(msg);
2694
+ break;
2695
+ case '__bridge/unexposed':
2696
+ this.handleIncomingUnexposed(msg);
2697
+ break;
2698
+ case '__bridge/publish':
2699
+ this.handleIncomingPublish(msg);
2700
+ break;
2701
+ case '__bridge/busReady':
2702
+ case '__bridge/drop':
2703
+ break;
2704
+ default:
2705
+ {
2706
+ // parseWireEnvelope admits any `__bridge/*` type; this catches
2707
+ // future message types a remote on a newer bridge sends to us.
2708
+ const unknown = msg;
2709
+ this.logger.warn('Received unknown bridge message type; ignoring', {
2710
+ type: unknown.type,
2711
+ remoteVersion: this.remoteVersion
2712
+ });
2713
+ break;
2714
+ }
2715
+ }
2716
+ }
2717
+ }
2718
+ get debug() {
2719
+ return {
2720
+ listLocalActions: () => Array.from(this.localActions.keys()),
2721
+ listRemoteActions: () => Array.from(this.remoteActions),
2722
+ listLocalValues: () => Array.from(this.localValues.keys()),
2723
+ listRemoteValues: () => Array.from(this.remoteValues.keys())
2724
+ };
2725
+ }
2726
+ dispose() {
2727
+ if (!this.disposed) {
2728
+ this.disposed = true;
2729
+ for (const pending of this.pendingCalls.values()) {
2730
+ clearTimeout(pending.timer);
2731
+ pending.reject(new DisposedError(pending.name));
2732
+ }
2733
+ this.pendingCalls.clear();
2734
+ this.callQueue.clear();
2735
+ }
2736
+ }
2737
+ async routeIncomingCall(msg) {
2738
+ if (this.localActions.has(msg.name)) {
2739
+ await this.invokeLocalHandler(msg);
2740
+ return;
2741
+ }
2742
+ let queued = this.callQueue.get(msg.name);
2743
+ if (!queued) {
2744
+ queued = [];
2745
+ this.callQueue.set(msg.name, queued);
2746
+ }
2747
+ // Evict TTL-expired and over-capacity entries so a remote that spams an
2748
+ // unknown action can't grow the queue without bound. Each evicted call
2749
+ // gets an error response so the caller fails fast.
2750
+ const cutoff = this.now() - INCOMING_CALL_QUEUE_TTL_MS;
2751
+ while (queued.length > 0) {
2752
+ const head = queued[0];
2753
+ if (!head || head.receivedAt >= cutoff) break;
2754
+ queued.shift();
2755
+ this.rejectQueued(head, 'expired');
2756
+ }
2757
+ while (queued.length >= INCOMING_CALL_QUEUE_MAX_PER_ACTION) {
2758
+ const evicted = queued.shift();
2759
+ if (!evicted) break;
2760
+ this.rejectQueued(evicted, 'queue_full');
2761
+ }
2762
+ queued.push({
2763
+ msg,
2764
+ receivedAt: this.now()
2765
+ });
2766
+ }
2767
+ rejectQueued(entry, code) {
2768
+ this.send({
2769
+ type: '__bridge/response',
2770
+ requestId: entry.msg.requestId,
2771
+ ok: false,
2772
+ code,
2773
+ message: `Queued call to "${entry.msg.name}" dropped (${code})`
2774
+ });
2775
+ }
2776
+ async invokeLocalHandler(msg) {
2777
+ // Handler may have been unregistered between queue push and drain. Reply
2778
+ // with an explicit error so the caller fails fast instead of timing out.
2779
+ const handler = this.localActions.get(msg.name);
2780
+ if (!handler) {
2781
+ this.send({
2782
+ type: '__bridge/response',
2783
+ requestId: msg.requestId,
2784
+ ok: false,
2785
+ code: 'unexposed',
2786
+ message: `No handler exposed for "${msg.name}"`
2787
+ });
2788
+ return;
2789
+ }
2790
+ try {
2791
+ const result = await handler(msg.args);
2792
+ this.send({
2793
+ type: '__bridge/response',
2794
+ requestId: msg.requestId,
2795
+ ok: true,
2796
+ result
2797
+ });
2798
+ } catch (error) {
2799
+ this.send({
2800
+ type: '__bridge/response',
2801
+ requestId: msg.requestId,
2802
+ ok: false,
2803
+ code: 'thrown',
2804
+ message: error instanceof Error ? error.message : String(error)
2805
+ });
2806
+ }
2807
+ }
2808
+ handleIncomingResponse(msg) {
2809
+ const pending = this.pendingCalls.get(msg.requestId);
2810
+ if (pending) {
2811
+ this.pendingCalls.delete(msg.requestId);
2812
+ clearTimeout(pending.timer);
2813
+ if (msg.ok) {
2814
+ pending.resolve(msg.result);
2815
+ } else {
2816
+ pending.reject(new HandlerError(pending.name, msg.code, msg.message));
2817
+ }
2818
+ } else {
2819
+ this.logger.warn('Response for unknown or already-resolved requestId', {
2820
+ requestId: msg.requestId
2821
+ });
2822
+ }
2823
+ }
2824
+ handleIncomingExposed(msg) {
2825
+ if (msg.kind === 'action') {
2826
+ if (!this.remoteActions.has(msg.name)) {
2827
+ this.remoteActions.add(msg.name);
2828
+ this.notifySet(this.actionListeners, msg.name, true, 'action availability');
2829
+ }
2830
+ } else {
2831
+ this.remoteValues.set(msg.name, msg.currentValue);
2832
+ this.notifySet(this.valueListeners, msg.name, msg.currentValue, 'remote value change');
2833
+ }
2834
+ }
2835
+ handleIncomingUnexposed(msg) {
2836
+ if (msg.kind === 'action') {
2837
+ if (this.remoteActions.delete(msg.name)) {
2838
+ this.notifySet(this.actionListeners, msg.name, false, 'action availability');
2839
+ }
2840
+ } else if (this.remoteValues.delete(msg.name)) {
2841
+ this.notifySet(this.valueListeners, msg.name, undefined, 'remote value change');
2842
+ }
2843
+ }
2844
+ handleIncomingPublish(msg) {
2845
+ this.remoteValues.set(msg.name, msg.value);
2846
+ this.notifySet(this.valueListeners, msg.name, msg.value, 'remote value change');
2847
+ }
2848
+ notifySet(subs, name, value, label) {
2849
+ const set = subs.get(name);
2850
+ if (set) {
2851
+ for (const callback of set) {
2852
+ try {
2853
+ callback(value);
2854
+ } catch (error) {
2855
+ this.logger.error(`Subscriber threw during ${label} notification`, {
2856
+ name,
2857
+ error
2858
+ });
2859
+ }
2860
+ }
2861
+ }
2862
+ }
2863
+ }
2864
+
2865
+ // Subset of window.__pcaBridge that bridge_web.ts needs.
2866
+
2867
+ const INBOUND_TYPES = ['__bridge/call', '__bridge/response', '__bridge/exposed', '__bridge/unexposed', '__bridge/publish'];
2868
+ class WebBridge extends BridgeCore {
2869
+ constructor(options = {}) {
2870
+ const bus = window.__pcaBridge;
2871
+ super({
2872
+ send: msg => {
2873
+ if (window.ReactNativeWebView) {
2874
+ try {
2875
+ window.ReactNativeWebView.postMessage(JSON.stringify(msg));
2876
+ } catch (error) {
2877
+ options.logger?.error('WebBridge postMessage failed', {
2878
+ error
2879
+ });
2880
+ }
2881
+ }
2882
+ },
2883
+ logger: options.logger,
2884
+ defaultTimeoutMs: options.defaultTimeoutMs
2885
+ });
2886
+ this.bus = bus;
2887
+ this.handlerFn = msg => {
2888
+ this.handleIncomingMessage(msg);
2889
+ };
2890
+ if (bus) {
2891
+ if (options.logger) {
2892
+ bus.setLogger(options.logger);
2893
+ }
2894
+ for (const type of INBOUND_TYPES) {
2895
+ bus.register(type, this.handlerFn);
2896
+ }
2897
+ if (window.ReactNativeWebView) {
2898
+ try {
2899
+ window.ReactNativeWebView.postMessage(JSON.stringify({
2900
+ type: '__bridge/busReady',
2901
+ bridgeVersion: BRIDGE_VERSION
2902
+ }));
2903
+ } catch (error) {
2904
+ options.logger?.error('WebBridge busReady post failed', {
2905
+ error
2906
+ });
2907
+ }
2908
+ }
2909
+ } else {
2910
+ options.logger?.warn('WebBridge constructed without window.__pcaBridge — bus init script may not have run');
2911
+ }
2912
+ }
2913
+ applyLogger(logger) {
2914
+ const applied = super.applyLogger(logger);
2915
+ if (applied && this.bus) {
2916
+ this.bus.setLogger(logger);
2917
+ }
2918
+ return applied;
2919
+ }
2920
+ dispose() {
2921
+ if (this.bus) {
2922
+ for (const type of INBOUND_TYPES) {
2923
+ this.bus.unregister(type, this.handlerFn);
2924
+ }
2925
+ }
2926
+ super.dispose();
2927
+ }
2928
+ }
2929
+ function getOrCreateWebBridge(options = {}) {
2930
+ if (window.__pcaWebBridge && options.logger) {
2931
+ window.__pcaWebBridge.applyLogger(options.logger);
2932
+ } else if (!window.__pcaWebBridge) {
2933
+ window.__pcaWebBridge = new WebBridge(options);
2934
+ }
2935
+ return window.__pcaWebBridge;
2936
+ }
2937
+
2938
+ const WebBridgeContext = /*#__PURE__*/React.createContext(null);
2939
+ function useWebBridge() {
2940
+ const ctxBridge = React.useContext(WebBridgeContext);
2941
+ if (ctxBridge) {
2942
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
2943
+ return ctxBridge;
2944
+ } else {
2945
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
2946
+ return getOrCreateWebBridge();
2947
+ }
2948
+ }
2949
+ function useBridgePublish(bridge, name, value) {
2950
+ // publish() dedups by shallow equality, so this effect is safe to re-run
2951
+ // on every value change without spamming wire messages.
2952
+ React.useEffect(() => {
2953
+ bridge.publish(name, value);
2954
+ }, [bridge, name, value]);
2955
+ React.useEffect(() => {
2956
+ return () => {
2957
+ bridge.unpublish(name);
2958
+ };
2959
+ }, [bridge, name]);
2960
+ }
2961
+
2238
2962
  const useSetCurrentRouteInfo = ({
2239
2963
  routes,
2240
2964
  app
2241
2965
  }) => {
2966
+ const bridge = useWebBridge();
2967
+ useBridgePublish(bridge, "routeInfo", {
2968
+ currentApp: app,
2969
+ currentRoutes: routes.map(({
2970
+ key,
2971
+ props: {
2972
+ to,
2973
+ href,
2974
+ active
2975
+ }
2976
+ }) => ({
2977
+ active,
2978
+ name: key,
2979
+ url: to !== null && to !== void 0 ? to : href
2980
+ }))
2981
+ });
2242
2982
  React.useEffect(() => {
2243
2983
  if (window.topbar) {
2244
2984
  window.topbar.routeInfo = {
@@ -2258,6 +2998,24 @@
2258
2998
  };
2259
2999
  }
2260
3000
  }, [routes, app]);
3001
+ // Publish info for when app goes back
3002
+ React.useEffect(() => window.addEventListener("pageshow", () => {
3003
+ bridge.publish("routeInfo", {
3004
+ currentApp: app,
3005
+ currentRoutes: routes.map(({
3006
+ key,
3007
+ props: {
3008
+ to,
3009
+ href,
3010
+ active
3011
+ }
3012
+ }) => ({
3013
+ active,
3014
+ name: key,
3015
+ url: to !== null && to !== void 0 ? to : href
3016
+ }))
3017
+ });
3018
+ }), []);
2261
3019
  };
2262
3020
 
2263
3021
  const useUpdateStore = () => React.useCallback(updates => {
@@ -3465,40 +4223,44 @@
3465
4223
  return fragment ? `${base}#${fragment}` : base;
3466
4224
  }
3467
4225
 
3468
- const handleSearchSelect = (hit, indexName) => {
3469
- const appName = indexName.split("_")[1];
3470
- const zendeskUrl = `https://${appName}.zendesk.com/hc/${hit.locale.locale}/articles/${hit.id}`;
3471
- const newTab = window.open(zendeskUrl, "_blank");
4226
+ // The article heading lives in the deepest populated hierarchy level (lvl0 is
4227
+ // always "Planning Center").
4228
+ const hitTitle = hierarchy => (hierarchy === null || hierarchy === void 0 ? void 0 : hierarchy.lvl6) || (hierarchy === null || hierarchy === void 0 ? void 0 : hierarchy.lvl5) || (hierarchy === null || hierarchy === void 0 ? void 0 : hierarchy.lvl4) || (hierarchy === null || hierarchy === void 0 ? void 0 : hierarchy.lvl3) || (hierarchy === null || hierarchy === void 0 ? void 0 : hierarchy.lvl2) || (hierarchy === null || hierarchy === void 0 ? void 0 : hierarchy.lvl1) || (hierarchy === null || hierarchy === void 0 ? void 0 : hierarchy.lvl0) || "";
4229
+ const handleSearchSelect = hit => {
4230
+ // `url` is always absolute (it is also the basis of objectID), so open it
4231
+ // directly. Bail out defensively if a record somehow arrives without one.
4232
+ if (!(hit === null || hit === void 0 ? void 0 : hit.url)) return;
4233
+ const newTab = window.open(hit.url, "_blank");
3472
4234
  if (newTab) {
3473
4235
  newTab.focus();
3474
4236
  }
3475
4237
  };
4238
+ // Mirrors @planningcenter/tapestry-react's Logo registry.
4239
+ const VALID_LOGO_NAMES = new Set(["api", "calendar", "check-ins", "church-center", "giving", "groups", "headcounts", "home", "login", "music-stand", "people", "planning-center", "publishing", "registrations", "services"]);
3476
4240
  const HeadingIcon = ({
3477
- sectionName,
3478
- currentAppName
4241
+ currentAppName,
4242
+ product
3479
4243
  }) => {
3480
- const lowerCaseSectionName = sectionName.toLowerCase();
3481
- if (lowerCaseSectionName === "account information" || lowerCaseSectionName === "recent updates") {
3482
- return /*#__PURE__*/React.createElement(tapestryReact.Icon, {
3483
- name: lowerCaseSectionName === "account information" ? "general.cogPerson" : "general.outlinedGenericFile",
3484
- color: "foregroundSecondary"
3485
- });
3486
- } else if (currentAppName === "Accounts") {
4244
+ const appName = product || currentAppName;
4245
+ if (!appName) return null;
4246
+ const logoName = appName.toLowerCase().replace(/\s+/g, "-");
4247
+ // Accounts has no tapestry Logo, so use the cog icon
4248
+ if (logoName === "accounts") {
3487
4249
  return /*#__PURE__*/React.createElement(tapestryReact.Icon, {
3488
4250
  name: "general.cog",
3489
4251
  color: "foregroundSecondary"
3490
4252
  });
3491
- } else {
3492
- return /*#__PURE__*/React.createElement(tapestryReact.StackView, {
3493
- width: 2,
3494
- height: 2
3495
- }, /*#__PURE__*/React.createElement(tapestryReact.Logo, {
3496
- name: currentAppName.toLowerCase().replace(/[\s]/, "-"),
3497
- size: "sm",
3498
- theme: "color",
3499
- markOnly: true
3500
- }));
3501
4253
  }
4254
+ if (!VALID_LOGO_NAMES.has(logoName)) return null;
4255
+ return /*#__PURE__*/React.createElement(tapestryReact.StackView, {
4256
+ width: 2,
4257
+ height: 2
4258
+ }, /*#__PURE__*/React.createElement(tapestryReact.Logo, {
4259
+ name: logoName,
4260
+ size: "sm",
4261
+ theme: "color",
4262
+ markOnly: true
4263
+ }));
3502
4264
  };
3503
4265
  const HitResult = ({
3504
4266
  hit,
@@ -3513,11 +4275,10 @@
3513
4275
  color: "neutralDarker",
3514
4276
  size: 4,
3515
4277
  whiteSpace: "normal"
3516
- }, hit.title));
4278
+ }, hitTitle(hit.hierarchy)));
3517
4279
  };
3518
4280
  const CustomSearchBox = ({
3519
- currentAppName,
3520
- indexName
4281
+ currentAppName
3521
4282
  }) => {
3522
4283
  const {
3523
4284
  query,
@@ -3528,7 +4289,10 @@
3528
4289
  results
3529
4290
  } = reactInstantsearchCore.useHits();
3530
4291
  const groups = results === null || results === void 0 ? void 0 : results.hits.reduce((acc, hit) => {
3531
- const key = hit.category.title;
4292
+ // A result with no url can't be opened, so don't render it at all.
4293
+ if (!hit.url) return acc;
4294
+ // Fall back so a record without a product never yields an "undefined" heading.
4295
+ const key = hit.product || currentAppName || "Other";
3532
4296
  if (!acc[key]) {
3533
4297
  acc[key] = [];
3534
4298
  }
@@ -3550,7 +4314,7 @@
3550
4314
  name: "general.search"
3551
4315
  }),
3552
4316
  onChange: e => setQuery(e.target.value),
3553
- onSelect: selectedValue => handleSearchSelect(selectedValue, indexName),
4317
+ onSelect: selectedValue => handleSearchSelect(selectedValue),
3554
4318
  placement: "bottom-start"
3555
4319
  }, !hits.length && (/*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(tapestryReact.StackView, {
3556
4320
  axis: "horizontal",
@@ -3562,7 +4326,7 @@
3562
4326
  size: 4,
3563
4327
  whiteSpace: "normal"
3564
4328
  }, "No results match your search")))), groups && Object.keys(groups).map(key => {
3565
- const hits = groups[key];
4329
+ const groupHits = groups[key];
3566
4330
  return /*#__PURE__*/React.createElement(React.Fragment, {
3567
4331
  key: key
3568
4332
  }, resultIndex > 0 && /*#__PURE__*/React.createElement(tapestryReact.Divider, {
@@ -3575,12 +4339,12 @@
3575
4339
  paddingVertical: 1
3576
4340
  }, /*#__PURE__*/React.createElement(HeadingIcon, {
3577
4341
  currentAppName: currentAppName,
3578
- sectionName: key
4342
+ product: key
3579
4343
  }), /*#__PURE__*/React.createElement(tapestryReact.Text, {
3580
4344
  size: 5,
3581
4345
  color: "foregroundSecondary",
3582
4346
  fontWeight: 500
3583
- }, key)), hits.map(hit => (/*#__PURE__*/React.createElement(HitResult, {
4347
+ }, key)), groupHits.map(hit => (/*#__PURE__*/React.createElement(HitResult, {
3584
4348
  key: hit.objectID,
3585
4349
  hit: hit,
3586
4350
  index: resultIndex++
@@ -3588,8 +4352,8 @@
3588
4352
  })));
3589
4353
  };
3590
4354
 
4355
+ const HELP_CENTER_INDEX = "help_center_english";
3591
4356
  const DocumentationSearch = ({
3592
- indexName,
3593
4357
  currentAppName
3594
4358
  }) => {
3595
4359
  const {
@@ -3600,13 +4364,13 @@
3600
4364
  } = useStore();
3601
4365
  const searchClient = algoliasearch(algoliaApplicationId, algoliaSearchOnlyApiKey);
3602
4366
  return /*#__PURE__*/React.createElement(reactInstantsearchCore.InstantSearch, {
3603
- indexName: indexName,
4367
+ indexName: HELP_CENTER_INDEX,
3604
4368
  searchClient: searchClient
3605
4369
  }, /*#__PURE__*/React.createElement(reactInstantsearchCore.Configure, {
3606
- hitsPerPage: 5
4370
+ hitsPerPage: 5,
4371
+ optionalFilters: currentAppName ? [`product:${currentAppName}`] : undefined
3607
4372
  }), /*#__PURE__*/React.createElement(CustomSearchBox, {
3608
- currentAppName: currentAppName,
3609
- indexName: indexName
4373
+ currentAppName: currentAppName
3610
4374
  }));
3611
4375
  };
3612
4376
 
@@ -3614,6 +4378,19 @@
3614
4378
  LoadingIndicator: LoadingIndicator$1,
3615
4379
  StackView: StackView$1
3616
4380
  } = addOns.components;
4381
+ function CalendlyWidget({
4382
+ url,
4383
+ pageSettings,
4384
+ prefill,
4385
+ styles
4386
+ }) {
4387
+ return url ? (/*#__PURE__*/React.createElement(reactCalendly.InlineWidget, {
4388
+ url: url,
4389
+ pageSettings: pageSettings,
4390
+ prefill: prefill,
4391
+ styles: styles
4392
+ })) : null;
4393
+ }
3617
4394
  function HelpdeskComponent({
3618
4395
  appName,
3619
4396
  closeDrawer,
@@ -3682,14 +4459,65 @@
3682
4459
  spacing: 1
3683
4460
  }, /*#__PURE__*/React.createElement(DrawerHeading, null, "Help and support"), /*#__PURE__*/React.createElement(LoadingIndicator$1, null)))),
3684
4461
  additionalComponents: {
3685
- // @ts-expect-error TODO: Relax type specification in add-ons-js to not require children
3686
- CalendlyWidget: reactCalendly.InlineWidget,
3687
- DocumentationSearch,
3688
- DrawerHeader,
4462
+ CalendlyWidget: {
4463
+ component: CalendlyWidget,
4464
+ properties: {
4465
+ pageSettings: {
4466
+ attribute: false
4467
+ },
4468
+ prefill: {
4469
+ attribute: false
4470
+ },
4471
+ styles: {
4472
+ attribute: false
4473
+ },
4474
+ url: {
4475
+ type: String
4476
+ }
4477
+ }
4478
+ },
4479
+ DocumentationSearch: {
4480
+ component: DocumentationSearch,
4481
+ properties: {
4482
+ currentAppName: {
4483
+ type: String
4484
+ }
4485
+ }
4486
+ },
4487
+ DrawerHeader: {
4488
+ component: DrawerHeader,
4489
+ properties: {
4490
+ closeDrawer: {
4491
+ event: false
4492
+ }
4493
+ }
4494
+ },
3689
4495
  DrawerHeading,
3690
- PicobotIframe,
4496
+ PicobotIframe: {
4497
+ component: PicobotIframe,
4498
+ properties: {
4499
+ currentPath: {
4500
+ attribute: false
4501
+ },
4502
+ onChangePath: {
4503
+ event: false,
4504
+ type: Function
4505
+ },
4506
+ onMessage: {
4507
+ event: false,
4508
+ type: Function
4509
+ }
4510
+ }
4511
+ },
3691
4512
  ScrollContainer,
3692
- SwitchView
4513
+ SwitchView: {
4514
+ component: SwitchView,
4515
+ properties: {
4516
+ activeIndex: {
4517
+ type: Number
4518
+ }
4519
+ }
4520
+ }
3693
4521
  },
3694
4522
  args: {
3695
4523
  appName,
@@ -4293,8 +5121,13 @@
4293
5121
  function NavLinkWithBadge({
4294
5122
  children,
4295
5123
  badge,
4296
- onClick,
4297
- ...rest
5124
+ borderBottomColor,
5125
+ borderBottomStyle,
5126
+ borderBottomWidth,
5127
+ color,
5128
+ fontSize,
5129
+ fontWeight,
5130
+ onClick
4298
5131
  }) {
4299
5132
  return /*#__PURE__*/React.createElement(tapestryReact.StackView, {
4300
5133
  as: "button",
@@ -4304,7 +5137,14 @@
4304
5137
  paddingBottom: 0.75,
4305
5138
  onClick: () => onClick(),
4306
5139
  spacing: 1,
4307
- ...rest
5140
+ ...{
5141
+ borderBottomColor,
5142
+ borderBottomStyle,
5143
+ borderBottomWidth,
5144
+ color,
5145
+ fontSize,
5146
+ fontWeight
5147
+ }
4308
5148
  }, children, badge && /*#__PURE__*/React.createElement(tapestryReact.Badge, {
4309
5149
  size: "sm",
4310
5150
  fontWeight: "600",
@@ -4356,11 +5196,55 @@
4356
5196
  spacing: 1
4357
5197
  }, /*#__PURE__*/React.createElement(DrawerHeading, null, "To do"), /*#__PURE__*/React.createElement(LoadingIndicator, null)))),
4358
5198
  additionalComponents: {
4359
- DrawerHeader,
5199
+ DrawerHeader: {
5200
+ component: DrawerHeader,
5201
+ properties: {
5202
+ closeDrawer: {
5203
+ event: false,
5204
+ type: Function
5205
+ }
5206
+ }
5207
+ },
4360
5208
  DrawerHeading,
4361
- NavLinkWithBadge,
5209
+ NavLinkWithBadge: {
5210
+ component: NavLinkWithBadge,
5211
+ properties: {
5212
+ badge: {
5213
+ attribute: false
5214
+ },
5215
+ borderBottomColor: {
5216
+ type: String
5217
+ },
5218
+ borderBottomStyle: {
5219
+ type: String
5220
+ },
5221
+ borderBottomWidth: {
5222
+ type: String
5223
+ },
5224
+ color: {
5225
+ type: String
5226
+ },
5227
+ fontSize: {
5228
+ attribute: false
5229
+ },
5230
+ fontWeight: {
5231
+ attribute: false
5232
+ },
5233
+ onClick: {
5234
+ event: false,
5235
+ type: Function
5236
+ }
5237
+ }
5238
+ },
4362
5239
  ScrollContainer,
4363
- SwitchView
5240
+ SwitchView: {
5241
+ component: SwitchView,
5242
+ properties: {
5243
+ activeIndex: {
5244
+ type: Number
5245
+ }
5246
+ }
5247
+ }
4364
5248
  },
4365
5249
  args: {
4366
5250
  closeDrawer,