@mediaradar/design-components 0.8.1 → 0.10.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.
package/dist/index.cjs CHANGED
@@ -28,6 +28,7 @@ __export(index_exports, {
28
28
  Button: () => Button,
29
29
  Card: () => Card,
30
30
  CheckBox: () => CheckBox,
31
+ CheckBoxBox: () => CheckBoxBox,
31
32
  ChipContent: () => ChipContent,
32
33
  DEFAULT_SEARCH_MATCH: () => DEFAULT_SEARCH_MATCH,
33
34
  DataScopeFilter: () => DataScopeFilter,
@@ -463,9 +464,39 @@ function OptionRow({
463
464
 
464
465
  // src/CheckBox.tsx
465
466
  var import_lucide_react4 = require("lucide-react");
467
+
468
+ // src/cn.ts
469
+ var import_clsx = require("clsx");
470
+ var import_tailwind_merge = require("tailwind-merge");
471
+ function cn(...inputs) {
472
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
473
+ }
474
+
475
+ // src/CheckBox.tsx
466
476
  var import_jsx_runtime4 = require("react/jsx-runtime");
467
- function CheckBox({ checked, indeterminate, onChange, ariaLabel, disabled, testId, title }) {
468
- const on = checked || indeterminate;
477
+ function boxClass(on, disabled) {
478
+ return cn(
479
+ "flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors",
480
+ on ? "border-transparent bg-brand-primaryAction text-white" : "border-border-interactive bg-input",
481
+ disabled && "cursor-not-allowed opacity-50"
482
+ );
483
+ }
484
+ function Glyph({ checked, indeterminate }) {
485
+ if (indeterminate) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react4.Minus, { size: 13, strokeWidth: 3 });
486
+ return checked ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react4.Check, { size: 13, strokeWidth: 3 }) : null;
487
+ }
488
+ function CheckBox({
489
+ checked,
490
+ indeterminate,
491
+ onChange,
492
+ ariaLabel,
493
+ disabled,
494
+ stopPropagation = true,
495
+ testId,
496
+ title,
497
+ className
498
+ }) {
499
+ const on = checked || !!indeterminate;
469
500
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
470
501
  "button",
471
502
  {
@@ -477,25 +508,26 @@ function CheckBox({ checked, indeterminate, onChange, ariaLabel, disabled, testI
477
508
  disabled,
478
509
  "data-testid": testId,
479
510
  onClick: (e) => {
480
- e.stopPropagation();
511
+ if (stopPropagation) e.stopPropagation();
481
512
  onChange();
482
513
  },
483
- className: [
484
- "flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors",
514
+ className: cn(
515
+ boxClass(on, disabled),
485
516
  "focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-border-focus",
486
- on ? "border-transparent bg-brand-primaryAction text-white" : "border-border-interactive bg-input hover:border-brand-primaryAction",
517
+ !on && "hover:border-brand-primaryAction",
487
518
  // Cancels the unchecked hover-border preview when disabled -- without
488
519
  // this, a disabled box still visually previews the "you can click
489
520
  // this" affordance on hover even though the click does nothing.
490
- // Found empirically reconciling this against insights-studio-
491
- // prototype's own (more complete) local CheckBox during its
492
- // migration -- that version already had this guard.
493
- disabled && "cursor-not-allowed opacity-50 hover:border-border-interactive"
494
- ].filter(Boolean).join(" "),
495
- children: indeterminate ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react4.Minus, { size: 13, strokeWidth: 3 }) : checked ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react4.Check, { size: 13, strokeWidth: 3 }) : null
521
+ disabled && "hover:border-border-interactive",
522
+ className
523
+ ),
524
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Glyph, { checked, indeterminate })
496
525
  }
497
526
  );
498
527
  }
528
+ function CheckBoxBox({ checked, indeterminate, disabled, className }) {
529
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "aria-hidden": "true", className: cn(boxClass(checked || !!indeterminate, disabled), className), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Glyph, { checked, indeterminate }) });
530
+ }
499
531
 
500
532
  // src/DateRangeFilter.tsx
501
533
  var import_react3 = require("react");
@@ -2631,15 +2663,6 @@ var import_react13 = require("react");
2631
2663
  var import_react_slot = require("@radix-ui/react-slot");
2632
2664
  var import_class_variance_authority = require("class-variance-authority");
2633
2665
  var import_lucide_react15 = require("lucide-react");
2634
-
2635
- // src/cn.ts
2636
- var import_clsx = require("clsx");
2637
- var import_tailwind_merge = require("tailwind-merge");
2638
- function cn(...inputs) {
2639
- return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
2640
- }
2641
-
2642
- // src/Button.tsx
2643
2666
  var import_jsx_runtime24 = require("react/jsx-runtime");
2644
2667
  var buttonVariants = (0, import_class_variance_authority.cva)(
2645
2668
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-control font-medium transition-all duration-(--mr-motion-duration-interactive) ease-interactive disabled:pointer-events-none disabled:opacity-40 focus-visible:outline-hidden focus-visible:ring-0",
@@ -2713,8 +2736,21 @@ function FilterDropdownFooter({
2713
2736
  var import_react14 = require("react");
2714
2737
  var import_react_dom5 = require("react-dom");
2715
2738
  var import_jsx_runtime26 = require("react/jsx-runtime");
2716
- function NameListModal({ title, initialName, submitLabel, onCancel, onSubmit }) {
2739
+ function NameListModal({
2740
+ title,
2741
+ initialName,
2742
+ submitLabel,
2743
+ onCancel,
2744
+ onSubmit,
2745
+ fieldLabel = "List name",
2746
+ placeholder = "e.g. Q4 prospecting targets",
2747
+ options
2748
+ }) {
2717
2749
  const [name, setName] = (0, import_react14.useState)(initialName);
2750
+ const [checked, setChecked] = (0, import_react14.useState)(
2751
+ () => Object.fromEntries((options ?? []).map((o) => [o.key, !!o.initial]))
2752
+ );
2753
+ const toggle = (key) => setChecked((c) => ({ ...c, [key]: !c[key] }));
2718
2754
  (0, import_react14.useEffect)(() => {
2719
2755
  const onKey = (e) => e.key === "Escape" && onCancel();
2720
2756
  window.addEventListener("keydown", onKey);
@@ -2739,7 +2775,7 @@ function NameListModal({ title, initialName, submitLabel, onCancel, onSubmit })
2739
2775
  children: [
2740
2776
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("h2", { className: "text-[16px] font-semibold text-text-primary", children: title }),
2741
2777
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "mt-4 flex flex-col gap-1.5", children: [
2742
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("label", { htmlFor: "account-list-name", className: "text-[12px] font-medium text-text-secondary", children: "List name" }),
2778
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("label", { htmlFor: "account-list-name", className: "text-[12px] font-medium text-text-secondary", children: fieldLabel }),
2743
2779
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2744
2780
  "input",
2745
2781
  {
@@ -2747,14 +2783,18 @@ function NameListModal({ title, initialName, submitLabel, onCancel, onSubmit })
2747
2783
  autoFocus: true,
2748
2784
  value: name,
2749
2785
  onChange: (e) => setName(e.target.value),
2750
- placeholder: "e.g. Q4 prospecting targets",
2786
+ placeholder,
2751
2787
  className: "h-10 rounded-control border border-border-default bg-input px-3 text-sm outline-hidden focus:border-brand-primaryAction"
2752
2788
  }
2753
2789
  )
2754
2790
  ] }),
2791
+ options && options.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "mt-4 flex flex-col gap-2.5", children: options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("label", { className: "flex cursor-pointer items-center gap-2.5 text-[13px] text-text-primary", children: [
2792
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(CheckBox, { checked: !!checked[o.key], onChange: () => toggle(o.key), ariaLabel: o.label }),
2793
+ o.label
2794
+ ] }, o.key)) }),
2755
2795
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "mt-5 flex items-center justify-end gap-2", children: [
2756
2796
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Button, { variant: "secondary", onClick: onCancel, children: "Cancel" }),
2757
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Button, { disabled: !name.trim(), onClick: () => onSubmit(name.trim()), children: submitLabel })
2797
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Button, { disabled: !name.trim(), onClick: () => onSubmit(name.trim(), checked), children: submitLabel })
2758
2798
  ] })
2759
2799
  ]
2760
2800
  }
@@ -2767,12 +2807,13 @@ function NameListModal({ title, initialName, submitLabel, onCancel, onSubmit })
2767
2807
 
2768
2808
  // src/ChipTrigger.tsx
2769
2809
  var import_jsx_runtime27 = require("react/jsx-runtime");
2770
- function chipTriggerClass() {
2810
+ function chipTriggerClass(variant = "box") {
2771
2811
  return cn(
2772
2812
  "flex h-11 items-center gap-2 rounded-control border bg-input px-2.5 text-text-secondary transition-colors hover:bg-subtle hover:text-text-primary focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-border-focus",
2773
- // Sits on the input fill, with a stroke dark enough to still read as a
2774
- // control against the warm canvas.
2775
- "border-border-strong"
2813
+ // Both variants sit on the input fill. `inline` carries the darker stroke
2814
+ // so it still reads as a control against the warm canvas with no boxed
2815
+ // shell behind it.
2816
+ variant === "inline" ? "border-border-strong" : "border-border-default"
2776
2817
  );
2777
2818
  }
2778
2819
  function ChipContent({ caption, value, icon }) {
@@ -3125,6 +3166,7 @@ var import_react15 = require("react");
3125
3166
  var import_lucide_react20 = require("lucide-react");
3126
3167
  var import_jsx_runtime34 = require("react/jsx-runtime");
3127
3168
  var MONTH_ABBR = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
3169
+ var QUARTER_ABBR = ["Q1", "Q2", "Q3", "Q4"];
3128
3170
  var PRESETS = ["Rolling 3 months", "Rolling 6 months", "Rolling 12 months"];
3129
3171
  var startOfToday = () => {
3130
3172
  const d = /* @__PURE__ */ new Date();
@@ -3173,6 +3215,7 @@ function MiniSelect({ value, onChange, options }) {
3173
3215
  }
3174
3216
  function DateRangePicker({
3175
3217
  defaultValue = "Rolling 6 months",
3218
+ granularity = "month",
3176
3219
  align = "left",
3177
3220
  presets = PRESETS,
3178
3221
  onChange,
@@ -3191,6 +3234,8 @@ function DateRangePicker({
3191
3234
  const [fromYr, setFromYr] = (0, import_react15.useState)(end.getFullYear() - 1);
3192
3235
  const [toMo, setToMo] = (0, import_react15.useState)(end.getMonth());
3193
3236
  const [toYr, setToYr] = (0, import_react15.useState)(end.getFullYear());
3237
+ const [fromQ, setFromQ] = (0, import_react15.useState)(Math.floor(end.getMonth() / 3));
3238
+ const [toQ, setToQ] = (0, import_react15.useState)(Math.floor(end.getMonth() / 3));
3194
3239
  (0, import_react15.useEffect)(() => {
3195
3240
  const [s, e] = resolveRange(value, end);
3196
3241
  onRangeChange?.(s, e);
@@ -3217,11 +3262,11 @@ function DateRangePicker({
3217
3262
  onChange?.(label);
3218
3263
  setOpen(false);
3219
3264
  };
3220
- const span = toYr * 12 + toMo - (fromYr * 12 + fromMo) + 1;
3265
+ const span = granularity === "year" ? toYr - fromYr + 1 : granularity === "quarter" ? toYr * 4 + toQ - (fromYr * 4 + fromQ) + 1 : toYr * 12 + toMo - (fromYr * 12 + fromMo) + 1;
3221
3266
  const valid = span >= 1;
3222
3267
  const applyCustom = () => {
3223
3268
  if (!valid) return;
3224
- const label = `${MONTH_ABBR[fromMo]} ${fromYr} \u2013 ${MONTH_ABBR[toMo]} ${toYr}`;
3269
+ const label = granularity === "year" ? `${fromYr} \u2013 ${toYr}` : granularity === "quarter" ? `${QUARTER_ABBR[fromQ]} ${fromYr} \u2013 ${QUARTER_ABBR[toQ]} ${toYr}` : `${MONTH_ABBR[fromMo]} ${fromYr} \u2013 ${MONTH_ABBR[toMo]} ${toYr}`;
3225
3270
  if (showFooter) setDraft(label);
3226
3271
  else commit(label);
3227
3272
  };
@@ -3260,14 +3305,16 @@ function DateRangePicker({
3260
3305
  /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("label", { className: "flex items-center justify-between gap-2 text-[12px] text-text-secondary", children: [
3261
3306
  "From",
3262
3307
  /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "flex gap-1", children: [
3263
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MiniSelect, { value: fromMo, onChange: setFromMo, options: MONTH_ABBR.map((mm, i) => ({ label: mm, value: i })) }),
3308
+ granularity === "month" && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MiniSelect, { value: fromMo, onChange: setFromMo, options: MONTH_ABBR.map((mm, i) => ({ label: mm, value: i })) }),
3309
+ granularity === "quarter" && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MiniSelect, { value: fromQ, onChange: setFromQ, options: QUARTER_ABBR.map((qq, i) => ({ label: qq, value: i })) }),
3264
3310
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MiniSelect, { value: fromYr, onChange: setFromYr, options: yearOptions.map((y) => ({ label: String(y), value: y })) })
3265
3311
  ] })
3266
3312
  ] }),
3267
3313
  /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("label", { className: "flex items-center justify-between gap-2 text-[12px] text-text-secondary", children: [
3268
3314
  "To",
3269
3315
  /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "flex gap-1", children: [
3270
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MiniSelect, { value: toMo, onChange: setToMo, options: MONTH_ABBR.map((mm, i) => ({ label: mm, value: i })) }),
3316
+ granularity === "month" && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MiniSelect, { value: toMo, onChange: setToMo, options: MONTH_ABBR.map((mm, i) => ({ label: mm, value: i })) }),
3317
+ granularity === "quarter" && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MiniSelect, { value: toQ, onChange: setToQ, options: QUARTER_ABBR.map((qq, i) => ({ label: qq, value: i })) }),
3271
3318
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MiniSelect, { value: toYr, onChange: setToYr, options: yearOptions.map((y) => ({ label: String(y), value: y })) })
3272
3319
  ] })
3273
3320
  ] }),
@@ -3296,6 +3343,8 @@ function MultiSelectFilter({
3296
3343
  caption,
3297
3344
  options = [],
3298
3345
  groups,
3346
+ counts,
3347
+ variant = "box",
3299
3348
  selected,
3300
3349
  onChange,
3301
3350
  allLabel = "All",
@@ -3353,14 +3402,18 @@ function MultiSelectFilter({
3353
3402
  return next;
3354
3403
  });
3355
3404
  const toggleGroup = (g) => setDraft((cur) => {
3356
- const ids = groupLeaves(g).map((c) => c.id);
3405
+ const ids = groupLeaves(g).map((c) => c.id).filter((id) => !counts || (counts[id] ?? 0) > 0 || cur.includes(id));
3406
+ if (!ids.length) return cur;
3357
3407
  const allSel = ids.every((id) => cur.includes(id));
3358
3408
  const next = allSel ? cur.filter((id) => !ids.includes(id)) : [.../* @__PURE__ */ new Set([...cur, ...ids])];
3359
3409
  if (next.length < min) return cur;
3360
3410
  return next;
3361
3411
  });
3412
+ const isAvailable = (id) => !counts || (counts[id] ?? 0) > 0 || draft.includes(id);
3413
+ const countFor = (id) => counts?.[id] ?? 0;
3362
3414
  const groupState = (g) => {
3363
- const ids = groupLeaves(g).map((c) => c.id);
3415
+ const ids = groupLeaves(g).map((c) => c.id).filter(isAvailable);
3416
+ if (!ids.length) return "none";
3364
3417
  const sel = ids.filter((id) => draft.includes(id)).length;
3365
3418
  return sel === 0 ? "none" : sel === ids.length ? "all" : "some";
3366
3419
  };
@@ -3368,10 +3421,12 @@ function MultiSelectFilter({
3368
3421
  const isAll = shown.length === 0 || fullIsAll && shown.length === leafOptions.length;
3369
3422
  const value = isAll ? allLabel : shown.length === 1 ? leafOptions.find((o) => o.id === shown[0])?.label ?? `${shown.length} selected` : `${shown.length} selected`;
3370
3423
  const ql = q.trim().toLowerCase();
3371
- const items = options.filter((o) => !ql || o.label.toLowerCase().includes(ql));
3424
+ const items = options.filter((o) => isAvailable(o.id) && (!ql || o.label.toLowerCase().includes(ql)));
3372
3425
  const visibleGroups = groups ? groups.map((g) => {
3373
- if (!ql || g.label.toLowerCase().includes(ql)) return g;
3374
- const kids = g.children.filter((c) => c.label.toLowerCase().includes(ql));
3426
+ const live = g.children.filter((c) => isAvailable(c.id));
3427
+ if (!live.length && !isAvailable(g.id)) return null;
3428
+ if (!ql || g.label.toLowerCase().includes(ql)) return { ...g, children: live };
3429
+ const kids = live.filter((c) => c.label.toLowerCase().includes(ql));
3375
3430
  return kids.length ? { ...g, children: kids } : null;
3376
3431
  }).filter((g) => g !== null) : [];
3377
3432
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: cn("relative", fullWidth && "w-full"), ref, children: [
@@ -3381,7 +3436,7 @@ function MultiSelectFilter({
3381
3436
  type: "button",
3382
3437
  onClick: () => open ? close() : openTray(),
3383
3438
  "aria-expanded": open,
3384
- className: cn(chipTriggerClass(), fullWidth && "w-full justify-between"),
3439
+ className: cn(chipTriggerClass(variant), fullWidth && "w-full justify-between"),
3385
3440
  children: [
3386
3441
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ChipContent, { caption, value, icon }),
3387
3442
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
@@ -3432,7 +3487,8 @@ function MultiSelectFilter({
3432
3487
  className: "flex w-full items-center gap-2.5 rounded-control px-2 py-2 text-left transition-colors hover:bg-subtle",
3433
3488
  children: [
3434
3489
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(TriCheck, { state }),
3435
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: cn("flex-1 text-[14px] text-text-primary", !plainParent && "font-semibold"), children: g.label })
3490
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: cn("flex-1 text-[14px] text-text-primary", !plainParent && "font-semibold"), children: g.label }),
3491
+ counts && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(OptionCount, { n: countFor(g.id) })
3436
3492
  ]
3437
3493
  }
3438
3494
  ),
@@ -3446,7 +3502,8 @@ function MultiSelectFilter({
3446
3502
  className: "flex w-full items-center gap-2.5 rounded-control py-2 pl-9 pr-2 text-left transition-colors hover:bg-subtle",
3447
3503
  children: [
3448
3504
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(TriCheck, { state: checked ? "all" : "none" }),
3449
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "flex-1 text-[14px] text-text-primary", children: c.label })
3505
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "flex-1 text-[14px] text-text-primary", children: c.label }),
3506
+ counts && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(OptionCount, { n: countFor(c.id) })
3450
3507
  ]
3451
3508
  },
3452
3509
  c.id
@@ -3472,7 +3529,8 @@ function MultiSelectFilter({
3472
3529
  children: checked && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react21.Check, { size: 13, strokeWidth: 3 })
3473
3530
  }
3474
3531
  ),
3475
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "flex-1 text-[14px] text-text-primary", children: o.label })
3532
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "flex-1 text-[14px] text-text-primary", children: o.label }),
3533
+ counts && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(OptionCount, { n: countFor(o.id) })
3476
3534
  ]
3477
3535
  },
3478
3536
  o.id
@@ -3513,6 +3571,13 @@ function TriCheck({ state }) {
3513
3571
  }
3514
3572
  );
3515
3573
  }
3574
+ function OptionCount({ n }) {
3575
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "shrink-0 text-[12px] tabular-nums text-text-tertiary", children: [
3576
+ "(",
3577
+ n.toLocaleString(),
3578
+ ")"
3579
+ ] });
3580
+ }
3516
3581
 
3517
3582
  // src/Toggle.tsx
3518
3583
  var import_jsx_runtime36 = require("react/jsx-runtime");
@@ -3599,6 +3664,7 @@ function Freshness({ level }) {
3599
3664
  Button,
3600
3665
  Card,
3601
3666
  CheckBox,
3667
+ CheckBoxBox,
3602
3668
  ChipContent,
3603
3669
  DEFAULT_SEARCH_MATCH,
3604
3670
  DataScopeFilter,