@aglyn/plugins-email 1.0.0-beta.219 → 1.0.0-beta.220

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.
Files changed (37) hide show
  1. package/package.json +18 -18
  2. package/src/lib/components/campaign-topic-options-widget.js +2 -2
  3. package/src/lib/components/campaign-topic-options-widget.js.map +1 -1
  4. package/src/lib/components/campaign-topic-select.js +3 -3
  5. package/src/lib/components/campaign-topic-select.js.map +1 -1
  6. package/src/lib/components/dynamic-list-rule-fields.d.ts +20 -4
  7. package/src/lib/components/dynamic-list-rule-fields.js +462 -411
  8. package/src/lib/components/dynamic-list-rule-fields.js.map +1 -1
  9. package/src/lib/components/email-topic-detail.d.ts +1 -1
  10. package/src/lib/components/email-topic-detail.js +5 -5
  11. package/src/lib/components/email-topic-detail.js.map +1 -1
  12. package/src/lib/components/email-topics-card.d.ts +1 -1
  13. package/src/lib/components/email-topics-card.js +6 -5
  14. package/src/lib/components/email-topics-card.js.map +1 -1
  15. package/src/lib/components/use-org-email-topics.d.ts +4 -4
  16. package/src/lib/components/use-org-email-topics.js +6 -5
  17. package/src/lib/components/use-org-email-topics.js.map +1 -1
  18. package/src/lib/list-import.js +2 -2
  19. package/src/lib/list-import.js.map +1 -1
  20. package/src/lib/model/email-topic-catalog.d.ts +36 -0
  21. package/src/lib/model/email-topic-catalog.js +36 -0
  22. package/src/lib/model/email-topic-catalog.js.map +1 -0
  23. package/src/lib/model/index.d.ts +0 -7
  24. package/src/lib/model/index.js +0 -6
  25. package/src/lib/model/index.js.map +1 -1
  26. package/src/lib/server-console.js +6 -2
  27. package/src/lib/server-console.js.map +1 -1
  28. package/src/lib/server-list-import.js +1 -1
  29. package/src/lib/server-list-import.js.map +1 -1
  30. package/src/lib/server-topic-index.d.ts +52 -0
  31. package/src/lib/server-topic-index.js +92 -0
  32. package/src/lib/server-topic-index.js.map +1 -0
  33. package/src/lib/server.js +11 -11
  34. package/src/lib/server.js.map +1 -1
  35. package/src/lib/unsubscribe-link.d.ts +1 -1
  36. package/src/lib/unsubscribe-link.js +1 -1
  37. package/src/lib/unsubscribe-link.js.map +1 -1
@@ -59,8 +59,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
59
59
  */ import { CONTACT_LIFECYCLE_STAGES, CONTACT_LIFECYCLE_STAGE_LABELS, CONTACT_SOURCE_LABELS, DYNAMIC_LIST_CUSTOM_OPS, normalizeDynamicListRule } from "@aglyn/aglyn";
60
60
  import { Autocomplete, Box, Button, Divider, MenuItem, Stack, TextField, Typography } from "@mui/material";
61
61
  import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
62
- import { useHostCampaigns, useOrgMemberOptions } from "@aglyn/tenant-feature-instance";
63
- import CampaignPicker from "@aglyn/shared-ui-email-campaigns/components/campaign-picker.component";
62
+ import { useSiteContainerOptions, useOrgMemberOptions } from "@aglyn/tenant-feature-instance";
63
+ import ContainerPicker from "@aglyn/tenant-feature-instance/components/container-picker";
64
+ import { useContentStable } from "@aglyn/shared-ui-jsx/hooks/use-content-stable";
64
65
  import { useOrgCompanyOptions } from "../hooks/use-org-company-options.js";
65
66
  import { useOrgContactFields } from "../hooks/use-org-contact-fields.js";
66
67
  import { useOrgContactSegments } from "../hooks/use-org-contact-segments.js";
@@ -632,7 +633,260 @@ const COMPANY_FIELD_SX = {
632
633
  sx: COMPANY_FIELD_SX
633
634
  });
634
635
  });
635
- export function DynamicListRuleFields(props) {
636
+ /** A text field that redraws only when what it shows changes (AGL-3423). */ const MemoTextField = /*#__PURE__*/ memo(TextField);
637
+ const MemoContainerPicker = /*#__PURE__*/ memo(ContainerPicker);
638
+ /**
639
+ * One `{ minWidth }` object per width, so a memoized field's `sx` is the same
640
+ * object on every render.
641
+ */ const MIN_WIDTHS = new Map();
642
+ const atLeast = (minWidth)=>{
643
+ let sx = MIN_WIDTHS.get(minWidth);
644
+ if (!sx) {
645
+ sx = {
646
+ minWidth
647
+ };
648
+ MIN_WIDTHS.set(minWidth, sx);
649
+ }
650
+ return sx;
651
+ };
652
+ const WRAP = {
653
+ flexWrap: 'wrap'
654
+ };
655
+ const WRAP_TOP = {
656
+ flexWrap: 'wrap',
657
+ alignItems: 'flex-start'
658
+ };
659
+ const SUMMARY_BOX = {
660
+ border: 1,
661
+ borderColor: 'divider',
662
+ borderRadius: 1,
663
+ p: 1.5
664
+ };
665
+ const CAMPAIGN_BOX = {
666
+ minWidth: 240,
667
+ flexGrow: 1,
668
+ maxWidth: 360
669
+ };
670
+ const CAPTION_BLOCK = {
671
+ display: 'block',
672
+ mt: 0.5
673
+ };
674
+ const MULTIPLE = {
675
+ select: {
676
+ multiple: true
677
+ }
678
+ };
679
+ const SHRUNK_LABEL = {
680
+ inputLabel: {
681
+ shrink: true
682
+ }
683
+ };
684
+ const STAGES_SELECT = {
685
+ select: {
686
+ multiple: true,
687
+ renderValue: (selected)=>selected.map((stage)=>{
688
+ var _CONTACT_LIFECYCLE_STAGE_LABELS_stage;
689
+ return (_CONTACT_LIFECYCLE_STAGE_LABELS_stage = CONTACT_LIFECYCLE_STAGE_LABELS[stage]) != null ? _CONTACT_LIFECYCLE_STAGE_LABELS_stage : stage;
690
+ }).join(', ')
691
+ }
692
+ };
693
+ /*
694
+ * The fixed menus, built once. A select's options are its children, and a
695
+ * child list built during render is a new prop on every render.
696
+ */ const SOURCE_MENU = Object.entries(SOURCE_LABELS).map(([value, label])=>/*#__PURE__*/ _jsx(MenuItem, {
697
+ value: value,
698
+ children: label
699
+ }, value));
700
+ const MATCH_MENU = [
701
+ /*#__PURE__*/ _jsx(MenuItem, {
702
+ value: "all",
703
+ children: 'All of the filters below'
704
+ }, "all"),
705
+ /*#__PURE__*/ _jsx(MenuItem, {
706
+ value: "any",
707
+ children: 'Any one of the filters below'
708
+ }, "any"),
709
+ /*#__PURE__*/ _jsx(MenuItem, {
710
+ value: "none",
711
+ children: 'Nobody matching all of them'
712
+ }, "none")
713
+ ];
714
+ const CAPTURE_MENU = Object.entries(CONTACT_SOURCE_LABELS).map(([value, label])=>/*#__PURE__*/ _jsx(MenuItem, {
715
+ value: value,
716
+ children: label
717
+ }, value));
718
+ const STAGE_MENU = CONTACT_LIFECYCLE_STAGES.map((stage)=>/*#__PURE__*/ _jsx(MenuItem, {
719
+ value: stage,
720
+ children: CONTACT_LIFECYCLE_STAGE_LABELS[stage]
721
+ }, stage));
722
+ const OP_MENU = DYNAMIC_LIST_CUSTOM_OPS.map((op)=>/*#__PURE__*/ _jsx(MenuItem, {
723
+ value: op,
724
+ children: CUSTOM_OP_LABELS[op]
725
+ }, op));
726
+ const CHECKED_MENU = [
727
+ /*#__PURE__*/ _jsx(MenuItem, {
728
+ value: "true",
729
+ children: 'Checked'
730
+ }, "true"),
731
+ /*#__PURE__*/ _jsx(MenuItem, {
732
+ value: "false",
733
+ children: 'Not checked'
734
+ }, "false")
735
+ ];
736
+ /** A multi-select hands back a string when only one option is chosen. */ const asArray = (value)=>typeof value === 'string' ? [
737
+ value
738
+ ] : value != null ? value : [];
739
+ /** Saved segments, views and audiences as menu items: a name, else the id. */ const menuOf = (rows)=>rows.map((row)=>{
740
+ var _row_name;
741
+ return /*#__PURE__*/ _jsx(MenuItem, {
742
+ value: row.$id,
743
+ children: (_row_name = row.name) != null ? _row_name : row.$id
744
+ }, row.$id);
745
+ });
746
+ /** The first choice of a saved segment or view: none. No stored id is empty. */ const NONE_ITEM = /*#__PURE__*/ _jsx(MenuItem, {
747
+ value: "",
748
+ children: 'None'
749
+ }, "");
750
+ /**
751
+ * One field condition: the field, how it compares, and the value — typed by
752
+ * the field's definition, so a number field hands the matcher a number and a
753
+ * choice field offers its own options, and a presence test asks for no value
754
+ * at all.
755
+ *
756
+ * Memoized, with handlers that write through the row's index, so a keystroke
757
+ * in one condition's value redraws that value and nothing else (AGL-3423).
758
+ */ const CustomClauseRow = /*#__PURE__*/ memo(function CustomClauseRow(props) {
759
+ var _clause_value, _definition_options;
760
+ const { index, clause, definition, fieldMenu, onUpdate, onRemove } = props;
761
+ const valueText = String((_clause_value = clause.value) != null ? _clause_value : '');
762
+ const setKey = useCallback((event)=>onUpdate(index, {
763
+ key: event.target.value,
764
+ value: ''
765
+ }), [
766
+ index,
767
+ onUpdate
768
+ ]);
769
+ const setOp = useCallback((event)=>onUpdate(index, {
770
+ op: event.target.value
771
+ }), [
772
+ index,
773
+ onUpdate
774
+ ]);
775
+ const setValue = useCallback((event)=>onUpdate(index, {
776
+ value: event.target.value
777
+ }), [
778
+ index,
779
+ onUpdate
780
+ ]);
781
+ const setChecked = useCallback((event)=>onUpdate(index, {
782
+ value: event.target.value === 'true'
783
+ }), [
784
+ index,
785
+ onUpdate
786
+ ]);
787
+ const setNumber = useCallback((event)=>{
788
+ // A number field stores a NUMBER, so the matcher compares 10 with 9
789
+ // and not "10" with "9"; half-typed text stays text until it parses,
790
+ // rather than becoming NaN.
791
+ const text = event.target.value;
792
+ const parsed = Number(text);
793
+ onUpdate(index, {
794
+ value: text.trim() !== '' && Number.isFinite(parsed) ? parsed : text
795
+ });
796
+ }, [
797
+ index,
798
+ onUpdate
799
+ ]);
800
+ const remove = useCallback(()=>onRemove(index), [
801
+ index,
802
+ onRemove
803
+ ]);
804
+ return /*#__PURE__*/ _jsxs(Stack, {
805
+ direction: "row",
806
+ spacing: 1,
807
+ useFlexGap: true,
808
+ sx: WRAP_TOP,
809
+ children: [
810
+ /*#__PURE__*/ _jsx(MemoTextField, {
811
+ select: true,
812
+ size: "small",
813
+ label: "Field",
814
+ value: clause.key,
815
+ onChange: setKey,
816
+ sx: atLeast(180),
817
+ children: fieldMenu
818
+ }),
819
+ /*#__PURE__*/ _jsx(MemoTextField, {
820
+ select: true,
821
+ size: "small",
822
+ label: "Condition",
823
+ value: clause.op,
824
+ onChange: setOp,
825
+ sx: atLeast(160),
826
+ children: OP_MENU
827
+ }),
828
+ PRESENCE_OPS.has(clause.op) ? null : (definition == null ? void 0 : definition.type) === 'select' ? /*#__PURE__*/ _jsx(MemoTextField, {
829
+ select: true,
830
+ size: "small",
831
+ label: "Value",
832
+ value: valueText,
833
+ onChange: setValue,
834
+ sx: atLeast(180),
835
+ children: ((_definition_options = definition.options) != null ? _definition_options : []).map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
836
+ value: option,
837
+ children: option
838
+ }, option))
839
+ }) : (definition == null ? void 0 : definition.type) === 'checkbox' ? /*#__PURE__*/ _jsx(MemoTextField, {
840
+ select: true,
841
+ size: "small",
842
+ label: "Value",
843
+ value: clause.value === true ? 'true' : clause.value === false ? 'false' : '',
844
+ onChange: setChecked,
845
+ sx: atLeast(140),
846
+ children: CHECKED_MENU
847
+ }) : (definition == null ? void 0 : definition.type) === 'number' ? /*#__PURE__*/ _jsx(MemoTextField, {
848
+ type: "number",
849
+ size: "small",
850
+ label: "Value",
851
+ value: valueText,
852
+ onChange: setNumber,
853
+ sx: atLeast(140)
854
+ }) : /*#__PURE__*/ _jsx(MemoTextField, {
855
+ type: (definition == null ? void 0 : definition.type) === 'date' ? 'date' : 'text',
856
+ size: "small",
857
+ label: "Value",
858
+ value: valueText,
859
+ onChange: setValue,
860
+ slotProps: (definition == null ? void 0 : definition.type) === 'date' ? SHRUNK_LABEL : undefined,
861
+ sx: atLeast(180)
862
+ }),
863
+ /*#__PURE__*/ _jsx(Button, {
864
+ size: "small",
865
+ onClick: remove,
866
+ children: 'Remove'
867
+ })
868
+ ]
869
+ });
870
+ });
871
+ /**
872
+ * The rule's controls.
873
+ *
874
+ * ## A keystroke redraws the field it lands in (AGL-3423)
875
+ *
876
+ * Every control writes the same draft, and the draft is the parent's state,
877
+ * so each keystroke renders this component again. Drawn inline, that redrew
878
+ * every control on the form — 27 inputs and some thousand components for one
879
+ * letter — and a keystroke that costs that much lets typed input queue, which
880
+ * is what turns a chip field's per-commit update into React #185. So every
881
+ * control is memoized and handed only props that keep their identity while
882
+ * what it shows is unchanged: handlers that write through the latest draft,
883
+ * menus built once or from their own data, and `sx` objects that are
884
+ * constants. The sentences above the controls are the one part that is meant
885
+ * to change with every keystroke.
886
+ *
887
+ * The component is memoized as a whole as well, so a keystroke in the page's
888
+ * own fields — the list's name — does not draw the rule at all.
889
+ */ export const DynamicListRuleFields = /*#__PURE__*/ memo(function DynamicListRuleFields(props) {
636
890
  var _team_error;
637
891
  const { scope, hostId, draft, onChange, listId } = props;
638
892
  const segmentDocs = useOrgContactSegments(scope);
@@ -645,7 +899,7 @@ export function DynamicListRuleFields(props) {
645
899
  * picker beside fields a reader came for; here the picker IS one of the
646
900
  * fields the reader came for, and the same reasoning that opens the segment
647
901
  * listen opens this one.
648
- */ const siteCampaigns = useHostCampaigns(hostId, {
902
+ */ const siteCampaigns = useSiteContainerOptions('campaign', hostId, {
649
903
  enabled: true
650
904
  });
651
905
  /*
@@ -717,6 +971,12 @@ export function DynamicListRuleFields(props) {
717
971
  listDocs,
718
972
  listId
719
973
  ]);
974
+ const memberNames = useMemo(()=>Object.fromEntries(team.options.map((option)=>[
975
+ option.uid,
976
+ option.label
977
+ ])), [
978
+ team.options
979
+ ]);
720
980
  /** Ids the pickers can show, so a clause names an audience rather than a uid. */ const names = useMemo(()=>({
721
981
  lists: Object.fromEntries(listDocs.map((row)=>{
722
982
  var _row_name;
@@ -740,10 +1000,7 @@ export function DynamicListRuleFields(props) {
740
1000
  option.value,
741
1001
  option.label
742
1002
  ])),
743
- members: Object.fromEntries(team.options.map((option)=>[
744
- option.uid,
745
- option.label
746
- ])),
1003
+ members: memberNames,
747
1004
  companies: companies.names,
748
1005
  fields: Object.fromEntries(fieldDefinitions.fields.map((field)=>[
749
1006
  field.key,
@@ -754,7 +1011,7 @@ export function DynamicListRuleFields(props) {
754
1011
  segments,
755
1012
  crmViews,
756
1013
  siteCampaigns.options,
757
- team.options,
1014
+ memberNames,
758
1015
  companies.names,
759
1016
  fieldDefinitions.fields
760
1017
  ]);
@@ -779,7 +1036,11 @@ export function DynamicListRuleFields(props) {
779
1036
  team.options,
780
1037
  draft.ownerUids
781
1038
  ]);
782
- const fieldOptions = useMemo(()=>{
1039
+ /*
1040
+ * Held by content: the options are read from the clauses' KEYS, and a value
1041
+ * typed into a clause is a new clause list with the same keys — which would
1042
+ * otherwise redraw every row's field picker on each keystroke (AGL-3423).
1043
+ */ const fieldOptions = useContentStable(useMemo(()=>{
783
1044
  const known = new Set(fieldDefinitions.fields.map((field)=>field.key));
784
1045
  return [
785
1046
  ...draft.custom.filter((clause)=>!known.has(clause.key)).map((clause)=>({
@@ -794,14 +1055,47 @@ export function DynamicListRuleFields(props) {
794
1055
  }, [
795
1056
  fieldDefinitions.fields,
796
1057
  draft.custom
1058
+ ]));
1059
+ /* The menus that come from data, rebuilt only when their data changes. */ const segmentMenu = useMemo(()=>[
1060
+ NONE_ITEM,
1061
+ ...menuOf(segments)
1062
+ ], [
1063
+ segments
1064
+ ]);
1065
+ const viewMenu = useMemo(()=>[
1066
+ NONE_ITEM,
1067
+ ...menuOf(crmViews)
1068
+ ], [
1069
+ crmViews
1070
+ ]);
1071
+ const listMenu = useMemo(()=>menuOf(lists), [
1072
+ lists
1073
+ ]);
1074
+ const ownerMenu = useMemo(()=>ownerOptions.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
1075
+ value: option.uid,
1076
+ children: option.label
1077
+ }, option.uid)), [
1078
+ ownerOptions
1079
+ ]);
1080
+ const ownerSelect = useMemo(()=>({
1081
+ select: {
1082
+ multiple: true,
1083
+ renderValue: (selected)=>selected.map((uid)=>named(uid, memberNames)).join(', ')
1084
+ }
1085
+ }), [
1086
+ memberNames
1087
+ ]);
1088
+ const fieldMenu = useMemo(()=>fieldOptions.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
1089
+ value: option.key,
1090
+ children: option.label
1091
+ }, option.key)), [
1092
+ fieldOptions
797
1093
  ]);
798
- /** The definition a clause's key names, when the org still has one. */ const definitionFor = (key)=>fieldDefinitions.fields.find((field)=>field.key === key);
799
- const set = (key, value)=>onChange(_extends({}, draft, {
800
- [key]: value
801
- }));
802
1094
  /*
803
- * `set('companyIds', …)` for the memoized company chips: the same write from
804
- * the last rendered draft, from a callback that keeps its identity.
1095
+ * Every control writes `{ ...draft, [key]: value }` from the last rendered
1096
+ * draft, through handlers that keep their identity from render to render —
1097
+ * a handler rebuilt per render is a changed prop, and a changed prop redraws
1098
+ * the field it is on.
805
1099
  */ const latest = useRef({
806
1100
  draft,
807
1101
  onChange
@@ -810,9 +1104,40 @@ export function DynamicListRuleFields(props) {
810
1104
  draft,
811
1105
  onChange
812
1106
  };
813
- const setCompanyIds = useCallback((companyIds)=>latest.current.onChange(_extends({}, latest.current.draft, {
814
- companyIds
1107
+ const set = useCallback((key, value)=>latest.current.onChange(_extends({}, latest.current.draft, {
1108
+ [key]: value
815
1109
  })), []);
1110
+ const on = useMemo(()=>{
1111
+ const typed = (key)=>(event)=>set(key, event.target.value);
1112
+ return {
1113
+ sources: (event)=>set('sources', asArray(event.target.value)),
1114
+ match: (event)=>set('match', event.target.value),
1115
+ createdAfter: typed('createdAfter'),
1116
+ createdBefore: typed('createdBefore'),
1117
+ formNames: typed('formNames'),
1118
+ campaignIds: (next)=>set('campaignIds', next),
1119
+ segmentId: typed('segmentId'),
1120
+ viewId: typed('viewId'),
1121
+ tags: typed('tags'),
1122
+ captureSources: (event)=>set('captureSources', asArray(event.target.value)),
1123
+ ownerUids: (event)=>set('ownerUids', asArray(event.target.value)),
1124
+ lifecycleStages: (event)=>set('lifecycleStages', asArray(event.target.value)),
1125
+ companyIds: (companyIds)=>set('companyIds', companyIds),
1126
+ engagedWithinDays: typed('engagedWithinDays'),
1127
+ ordersCountAtLeast: typed('ordersCountAtLeast'),
1128
+ ltvAtLeast: typed('ltvAtLeast'),
1129
+ lastPurchaseWithinDays: typed('lastPurchaseWithinDays'),
1130
+ noPurchaseForDays: typed('noPurchaseForDays'),
1131
+ openedWithinDays: typed('openedWithinDays'),
1132
+ clickedWithinDays: typed('clickedWithinDays'),
1133
+ notOpenedForDays: typed('notOpenedForDays'),
1134
+ notClickedForDays: typed('notClickedForDays'),
1135
+ inListIds: (event)=>set('inListIds', asArray(event.target.value)),
1136
+ notInListIds: (event)=>set('notInListIds', asArray(event.target.value))
1137
+ };
1138
+ }, [
1139
+ set
1140
+ ]);
816
1141
  const companyIds = draft.companyIds;
817
1142
  const companyNames = companies.names;
818
1143
  const chosenCompanies = useMemo(()=>companyIds.map((id)=>{
@@ -833,39 +1158,39 @@ export function DynamicListRuleFields(props) {
833
1158
  * a value typed for a number field is not a value for the select that
834
1159
  * replaced it; changing the operator to a presence test removes it, so the
835
1160
  * stored clause carries no value the sentence does not read.
836
- */ const addClause = ()=>{
1161
+ */ const addClause = useCallback(()=>{
837
1162
  const first = fieldOptions[0];
838
1163
  if (!first) return;
839
1164
  set('custom', [
840
- ...draft.custom,
1165
+ ...latest.current.draft.custom,
841
1166
  {
842
1167
  key: first.key,
843
1168
  op: 'eq',
844
1169
  value: ''
845
1170
  }
846
1171
  ]);
847
- };
848
- const updateClause = (index, patch)=>set('custom', draft.custom.map((clause, at)=>{
1172
+ }, [
1173
+ fieldOptions,
1174
+ set
1175
+ ]);
1176
+ const updateClause = useCallback((index, patch)=>set('custom', latest.current.draft.custom.map((clause, at)=>{
849
1177
  if (at !== index) return clause;
850
1178
  const next = _extends({}, clause, patch);
851
1179
  if (PRESENCE_OPS.has(next.op)) delete next.value;
852
1180
  return next;
853
- }));
854
- const removeClause = (index)=>set('custom', draft.custom.filter((_, at)=>at !== index));
855
- /** A multi-select hands back a string when only one option is chosen. */ const asArray = (value)=>typeof value === 'string' ? [
856
- value
857
- ] : value != null ? value : [];
1181
+ })), [
1182
+ set
1183
+ ]);
1184
+ const removeClause = useCallback((index)=>set('custom', latest.current.draft.custom.filter((_, at)=>at !== index)), [
1185
+ set
1186
+ ]);
1187
+ /** The definition a clause's key names, when the org still has one. */ const definitionFor = (key)=>fieldDefinitions.fields.find((field)=>field.key === key);
858
1188
  const contactsOnly = draft.sources.includes('contacts');
859
1189
  return /*#__PURE__*/ _jsxs(Stack, {
860
1190
  spacing: 1.5,
861
1191
  children: [
862
1192
  /*#__PURE__*/ _jsxs(Box, {
863
- sx: {
864
- border: 1,
865
- borderColor: 'divider',
866
- borderRadius: 1,
867
- p: 1.5
868
- },
1193
+ sx: SUMMARY_BOX,
869
1194
  children: [
870
1195
  /*#__PURE__*/ _jsx(Typography, {
871
1196
  variant: "overline",
@@ -882,105 +1207,62 @@ export function DynamicListRuleFields(props) {
882
1207
  direction: "row",
883
1208
  spacing: 1,
884
1209
  useFlexGap: true,
885
- sx: {
886
- flexWrap: 'wrap'
887
- },
1210
+ sx: WRAP,
888
1211
  children: [
889
- /*#__PURE__*/ _jsx(TextField, {
1212
+ /*#__PURE__*/ _jsx(MemoTextField, {
890
1213
  select: true,
891
1214
  size: "small",
892
1215
  label: "People from",
893
1216
  value: draft.sources,
894
- onChange: (event)=>set('sources', asArray(event.target.value)),
895
- slotProps: {
896
- select: {
897
- multiple: true
898
- }
899
- },
900
- sx: {
901
- minWidth: 220
902
- },
903
- children: Object.entries(SOURCE_LABELS).map(([value, label])=>/*#__PURE__*/ _jsx(MenuItem, {
904
- value: value,
905
- children: label
906
- }, value))
1217
+ onChange: on.sources,
1218
+ slotProps: MULTIPLE,
1219
+ sx: atLeast(220),
1220
+ children: SOURCE_MENU
907
1221
  }),
908
- /*#__PURE__*/ _jsxs(TextField, {
1222
+ /*#__PURE__*/ _jsx(MemoTextField, {
909
1223
  select: true,
910
1224
  size: "small",
911
1225
  label: "Match",
912
1226
  value: draft.match,
913
- onChange: (event)=>set('match', event.target.value),
1227
+ onChange: on.match,
914
1228
  helperText: "A saved segment always applies",
915
- sx: {
916
- minWidth: 210
917
- },
918
- children: [
919
- /*#__PURE__*/ _jsx(MenuItem, {
920
- value: "all",
921
- children: 'All of the filters below'
922
- }),
923
- /*#__PURE__*/ _jsx(MenuItem, {
924
- value: "any",
925
- children: 'Any one of the filters below'
926
- }),
927
- /*#__PURE__*/ _jsx(MenuItem, {
928
- value: "none",
929
- children: 'Nobody matching all of them'
930
- })
931
- ]
1229
+ sx: atLeast(210),
1230
+ children: MATCH_MENU
932
1231
  }),
933
- /*#__PURE__*/ _jsx(TextField, {
1232
+ /*#__PURE__*/ _jsx(MemoTextField, {
934
1233
  type: "date",
935
1234
  size: "small",
936
1235
  label: "Created after",
937
1236
  value: draft.createdAfter,
938
- onChange: (event)=>set('createdAfter', event.target.value),
939
- slotProps: {
940
- inputLabel: {
941
- shrink: true
942
- }
943
- },
944
- sx: {
945
- minWidth: 170
946
- }
1237
+ onChange: on.createdAfter,
1238
+ slotProps: SHRUNK_LABEL,
1239
+ sx: atLeast(170)
947
1240
  }),
948
- /*#__PURE__*/ _jsx(TextField, {
1241
+ /*#__PURE__*/ _jsx(MemoTextField, {
949
1242
  type: "date",
950
1243
  size: "small",
951
1244
  label: "Created before",
952
1245
  value: draft.createdBefore,
953
- onChange: (event)=>set('createdBefore', event.target.value),
954
- slotProps: {
955
- inputLabel: {
956
- shrink: true
957
- }
958
- },
959
- sx: {
960
- minWidth: 170
961
- }
1246
+ onChange: on.createdBefore,
1247
+ slotProps: SHRUNK_LABEL,
1248
+ sx: atLeast(170)
962
1249
  }),
963
- /*#__PURE__*/ _jsx(TextField, {
1250
+ /*#__PURE__*/ _jsx(MemoTextField, {
964
1251
  size: "small",
965
1252
  label: "Submitted form",
966
1253
  placeholder: "Contact us",
967
1254
  helperText: "Form submissions only",
968
1255
  value: draft.formNames,
969
- onChange: (event)=>set('formNames', event.target.value),
970
- sx: {
971
- minWidth: 180
972
- }
1256
+ onChange: on.formNames,
1257
+ sx: atLeast(180)
973
1258
  }),
974
1259
  /*#__PURE__*/ _jsx(Box, {
975
- sx: {
976
- minWidth: 240,
977
- flexGrow: 1,
978
- maxWidth: 360
979
- },
980
- children: /*#__PURE__*/ _jsx(CampaignPicker, {
1260
+ sx: CAMPAIGN_BOX,
1261
+ children: /*#__PURE__*/ _jsx(MemoContainerPicker, {
1262
+ kind: "campaign",
981
1263
  options: siteCampaigns.options,
982
1264
  value: draft.campaignIds,
983
- onChange: (next)=>set('campaignIds', next),
1265
+ onChange: on.campaignIds,
984
1266
  label: "In campaign",
985
1267
  helperText: "Contacts and form submissions filed under any campaign picked. Being in a campaign is not consent to be emailed.",
986
1268
  empty: siteCampaigns.ready && !siteCampaigns.options.length,
@@ -1004,84 +1286,46 @@ export function DynamicListRuleFields(props) {
1004
1286
  direction: "row",
1005
1287
  spacing: 1,
1006
1288
  useFlexGap: true,
1007
- sx: {
1008
- flexWrap: 'wrap'
1009
- },
1289
+ sx: WRAP,
1010
1290
  children: [
1011
- /*#__PURE__*/ _jsxs(TextField, {
1291
+ /*#__PURE__*/ _jsx(MemoTextField, {
1012
1292
  select: true,
1013
1293
  size: "small",
1014
1294
  label: "Saved segment",
1015
1295
  value: draft.segmentId,
1016
- onChange: (event)=>set('segmentId', event.target.value),
1296
+ onChange: on.segmentId,
1017
1297
  helperText: "Reuses that segment's tags and sources",
1018
- sx: {
1019
- minWidth: 200
1020
- },
1021
- children: [
1022
- /*#__PURE__*/ _jsx(MenuItem, {
1023
- value: "",
1024
- children: 'None'
1025
- }),
1026
- segments.map((segment)=>{
1027
- var _segment_name;
1028
- return /*#__PURE__*/ _jsx(MenuItem, {
1029
- value: segment.$id,
1030
- children: (_segment_name = segment.name) != null ? _segment_name : segment.$id
1031
- }, segment.$id);
1032
- })
1033
- ]
1298
+ sx: atLeast(200),
1299
+ children: segmentMenu
1034
1300
  }),
1035
- /*#__PURE__*/ _jsxs(TextField, {
1301
+ /*#__PURE__*/ _jsx(MemoTextField, {
1036
1302
  select: true,
1037
1303
  size: "small",
1038
1304
  label: "Saved view",
1039
1305
  value: draft.viewId,
1040
- onChange: (event)=>set('viewId', event.target.value),
1306
+ onChange: on.viewId,
1041
1307
  helperText: "Reuses that Contacts view's filters",
1042
- sx: {
1043
- minWidth: 200
1044
- },
1045
- children: [
1046
- /*#__PURE__*/ _jsx(MenuItem, {
1047
- value: "",
1048
- children: 'None'
1049
- }),
1050
- crmViews.map((view)=>/*#__PURE__*/ _jsx(MenuItem, {
1051
- value: view.$id,
1052
- children: view.name
1053
- }, view.$id))
1054
- ]
1308
+ sx: atLeast(200),
1309
+ children: viewMenu
1055
1310
  }),
1056
- /*#__PURE__*/ _jsx(TextField, {
1311
+ /*#__PURE__*/ _jsx(MemoTextField, {
1057
1312
  size: "small",
1058
1313
  label: "Tagged",
1059
1314
  placeholder: "vip, wholesale",
1060
1315
  value: draft.tags,
1061
- onChange: (event)=>set('tags', event.target.value),
1062
- sx: {
1063
- minWidth: 180
1064
- }
1316
+ onChange: on.tags,
1317
+ sx: atLeast(180)
1065
1318
  }),
1066
- /*#__PURE__*/ _jsx(TextField, {
1319
+ /*#__PURE__*/ _jsx(MemoTextField, {
1067
1320
  select: true,
1068
1321
  size: "small",
1069
1322
  label: "Captured by",
1070
1323
  value: draft.captureSources,
1071
- onChange: (event)=>set('captureSources', asArray(event.target.value)),
1072
- slotProps: {
1073
- select: {
1074
- multiple: true
1075
- }
1076
- },
1324
+ onChange: on.captureSources,
1325
+ slotProps: MULTIPLE,
1077
1326
  helperText: "How the contact reached you",
1078
- sx: {
1079
- minWidth: 200
1080
- },
1081
- children: Object.entries(CONTACT_SOURCE_LABELS).map(([value, label])=>/*#__PURE__*/ _jsx(MenuItem, {
1082
- value: value,
1083
- children: label
1084
- }, value))
1327
+ sx: atLeast(200),
1328
+ children: CAPTURE_MENU
1085
1329
  })
1086
1330
  ]
1087
1331
  }),
@@ -1089,55 +1333,30 @@ export function DynamicListRuleFields(props) {
1089
1333
  direction: "row",
1090
1334
  spacing: 1,
1091
1335
  useFlexGap: true,
1092
- sx: {
1093
- flexWrap: 'wrap'
1094
- },
1336
+ sx: WRAP,
1095
1337
  children: [
1096
- /*#__PURE__*/ _jsx(TextField, {
1338
+ /*#__PURE__*/ _jsx(MemoTextField, {
1097
1339
  select: true,
1098
1340
  size: "small",
1099
1341
  label: "Owned by",
1100
1342
  value: draft.ownerUids,
1101
- onChange: (event)=>set('ownerUids', asArray(event.target.value)),
1102
- slotProps: {
1103
- select: {
1104
- multiple: true,
1105
- renderValue: (selected)=>selected.map((uid)=>named(uid, names.members)).join(', ')
1106
- }
1107
- },
1343
+ onChange: on.ownerUids,
1344
+ slotProps: ownerSelect,
1108
1345
  error: Boolean(team.error),
1109
1346
  helperText: (_team_error = team.error) != null ? _team_error : 'Any of these team members. Unowned contacts are left out.',
1110
- sx: {
1111
- minWidth: 220
1112
- },
1113
- children: ownerOptions.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
1114
- value: option.uid,
1115
- children: option.label
1116
- }, option.uid))
1347
+ sx: atLeast(220),
1348
+ children: ownerMenu
1117
1349
  }),
1118
- /*#__PURE__*/ _jsx(TextField, {
1350
+ /*#__PURE__*/ _jsx(MemoTextField, {
1119
1351
  select: true,
1120
1352
  size: "small",
1121
1353
  label: "Lifecycle stage",
1122
1354
  value: draft.lifecycleStages,
1123
- onChange: (event)=>set('lifecycleStages', asArray(event.target.value)),
1124
- slotProps: {
1125
- select: {
1126
- multiple: true,
1127
- renderValue: (selected)=>selected.map((stage)=>{
1128
- var _CONTACT_LIFECYCLE_STAGE_LABELS_stage;
1129
- return (_CONTACT_LIFECYCLE_STAGE_LABELS_stage = CONTACT_LIFECYCLE_STAGE_LABELS[stage]) != null ? _CONTACT_LIFECYCLE_STAGE_LABELS_stage : stage;
1130
- }).join(', ')
1131
- }
1132
- },
1355
+ onChange: on.lifecycleStages,
1356
+ slotProps: STAGES_SELECT,
1133
1357
  helperText: "Any of these stages",
1134
- sx: {
1135
- minWidth: 200
1136
- },
1137
- children: CONTACT_LIFECYCLE_STAGES.map((stage)=>/*#__PURE__*/ _jsx(MenuItem, {
1138
- value: stage,
1139
- children: CONTACT_LIFECYCLE_STAGE_LABELS[stage]
1140
- }, stage))
1358
+ sx: atLeast(200),
1359
+ children: STAGE_MENU
1141
1360
  }),
1142
1361
  /*#__PURE__*/ _jsx(RuleCompaniesField, {
1143
1362
  hits: companies.hits,
@@ -1145,146 +1364,27 @@ export function DynamicListRuleFields(props) {
1145
1364
  search: companySearch,
1146
1365
  searching: companies.searching,
1147
1366
  onSearch: setCompanySearch,
1148
- onChange: setCompanyIds
1367
+ onChange: on.companyIds
1149
1368
  }),
1150
- /*#__PURE__*/ _jsx(TextField, {
1369
+ /*#__PURE__*/ _jsx(MemoTextField, {
1151
1370
  type: "number",
1152
1371
  size: "small",
1153
1372
  label: "Engaged with a campaign within (days)",
1154
1373
  helperText: "Opened or clicked one of your campaigns. Never engaged is left out.",
1155
1374
  value: draft.engagedWithinDays,
1156
- onChange: (event)=>set('engagedWithinDays', event.target.value),
1157
- sx: {
1158
- minWidth: 280
1159
- }
1375
+ onChange: on.engagedWithinDays,
1376
+ sx: atLeast(280)
1160
1377
  })
1161
1378
  ]
1162
1379
  }),
1163
- draft.custom.map((clause, index)=>{
1164
- var _clause_value, _definition_options;
1165
- const definition = definitionFor(clause.key);
1166
- const valueText = String((_clause_value = clause.value) != null ? _clause_value : '');
1167
- return /*#__PURE__*/ _jsxs(Stack, {
1168
- direction: "row",
1169
- spacing: 1,
1170
- useFlexGap: true,
1171
- sx: {
1172
- flexWrap: 'wrap',
1173
- alignItems: 'flex-start'
1174
- },
1175
- children: [
1176
- /*#__PURE__*/ _jsx(TextField, {
1177
- select: true,
1178
- size: "small",
1179
- label: "Field",
1180
- value: clause.key,
1181
- onChange: (event)=>updateClause(index, {
1182
- key: event.target.value,
1183
- value: ''
1184
- }),
1185
- sx: {
1186
- minWidth: 180
1187
- },
1188
- children: fieldOptions.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
1189
- value: option.key,
1190
- children: option.label
1191
- }, option.key))
1192
- }),
1193
- /*#__PURE__*/ _jsx(TextField, {
1194
- select: true,
1195
- size: "small",
1196
- label: "Condition",
1197
- value: clause.op,
1198
- onChange: (event)=>updateClause(index, {
1199
- op: event.target.value
1200
- }),
1201
- sx: {
1202
- minWidth: 160
1203
- },
1204
- children: DYNAMIC_LIST_CUSTOM_OPS.map((op)=>/*#__PURE__*/ _jsx(MenuItem, {
1205
- value: op,
1206
- children: CUSTOM_OP_LABELS[op]
1207
- }, op))
1208
- }),
1209
- PRESENCE_OPS.has(clause.op) ? null : (definition == null ? void 0 : definition.type) === 'select' ? /*#__PURE__*/ _jsx(TextField, {
1210
- select: true,
1211
- size: "small",
1212
- label: "Value",
1213
- value: valueText,
1214
- onChange: (event)=>updateClause(index, {
1215
- value: event.target.value
1216
- }),
1217
- sx: {
1218
- minWidth: 180
1219
- },
1220
- children: ((_definition_options = definition.options) != null ? _definition_options : []).map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
1221
- value: option,
1222
- children: option
1223
- }, option))
1224
- }) : (definition == null ? void 0 : definition.type) === 'checkbox' ? /*#__PURE__*/ _jsxs(TextField, {
1225
- select: true,
1226
- size: "small",
1227
- label: "Value",
1228
- value: clause.value === true ? 'true' : clause.value === false ? 'false' : '',
1229
- onChange: (event)=>updateClause(index, {
1230
- value: event.target.value === 'true'
1231
- }),
1232
- sx: {
1233
- minWidth: 140
1234
- },
1235
- children: [
1236
- /*#__PURE__*/ _jsx(MenuItem, {
1237
- value: "true",
1238
- children: 'Checked'
1239
- }),
1240
- /*#__PURE__*/ _jsx(MenuItem, {
1241
- value: "false",
1242
- children: 'Not checked'
1243
- })
1244
- ]
1245
- }) : (definition == null ? void 0 : definition.type) === 'number' ? /*#__PURE__*/ _jsx(TextField, {
1246
- type: "number",
1247
- size: "small",
1248
- label: "Value",
1249
- value: valueText,
1250
- onChange: (event)=>{
1251
- // A number field stores a NUMBER, so the matcher compares
1252
- // 10 with 9 and not "10" with "9"; half-typed text stays
1253
- // text until it parses, rather than becoming NaN.
1254
- const text = event.target.value;
1255
- const parsed = Number(text);
1256
- updateClause(index, {
1257
- value: text.trim() !== '' && Number.isFinite(parsed) ? parsed : text
1258
- });
1259
- },
1260
- sx: {
1261
- minWidth: 140
1262
- }
1263
- }) : /*#__PURE__*/ _jsx(TextField, {
1264
- type: (definition == null ? void 0 : definition.type) === 'date' ? 'date' : 'text',
1265
- size: "small",
1266
- label: "Value",
1267
- value: valueText,
1268
- onChange: (event)=>updateClause(index, {
1269
- value: event.target.value
1270
- }),
1271
- slotProps: (definition == null ? void 0 : definition.type) === 'date' ? {
1272
- inputLabel: {
1273
- shrink: true
1274
- }
1275
- } : undefined,
1276
- sx: {
1277
- minWidth: 180
1278
- }
1279
- }),
1280
- /*#__PURE__*/ _jsx(Button, {
1281
- size: "small",
1282
- onClick: ()=>removeClause(index),
1283
- children: 'Remove'
1284
- })
1285
- ]
1286
- }, index);
1287
- }),
1380
+ draft.custom.map((clause, index)=>/*#__PURE__*/ _jsx(CustomClauseRow, {
1381
+ index: index,
1382
+ clause: clause,
1383
+ definition: definitionFor(clause.key),
1384
+ fieldMenu: fieldMenu,
1385
+ onUpdate: updateClause,
1386
+ onRemove: removeClause
1387
+ }, index)),
1288
1388
  /*#__PURE__*/ _jsxs(Box, {
1289
1389
  children: [
1290
1390
  /*#__PURE__*/ _jsx(Button, {
@@ -1297,10 +1397,7 @@ export function DynamicListRuleFields(props) {
1297
1397
  fieldDefinitions.ready && !fieldDefinitions.fields.length ? /*#__PURE__*/ _jsx(Typography, {
1298
1398
  variant: "caption",
1299
1399
  color: "text.secondary",
1300
- sx: {
1301
- display: 'block',
1302
- mt: 0.5
1303
- },
1400
+ sx: CAPTION_BLOCK,
1304
1401
  children: 'This workspace has no custom contact fields yet. Define them ' + 'under CRM → Fields to filter on them here.'
1305
1402
  }) : null
1306
1403
  ]
@@ -1314,51 +1411,41 @@ export function DynamicListRuleFields(props) {
1314
1411
  direction: "row",
1315
1412
  spacing: 1,
1316
1413
  useFlexGap: true,
1317
- sx: {
1318
- flexWrap: 'wrap'
1319
- },
1414
+ sx: WRAP,
1320
1415
  children: [
1321
- /*#__PURE__*/ _jsx(TextField, {
1416
+ /*#__PURE__*/ _jsx(MemoTextField, {
1322
1417
  type: "number",
1323
1418
  size: "small",
1324
1419
  label: "Orders at least",
1325
1420
  value: draft.ordersCountAtLeast,
1326
- onChange: (event)=>set('ordersCountAtLeast', event.target.value),
1327
- sx: {
1328
- minWidth: 160
1329
- }
1421
+ onChange: on.ordersCountAtLeast,
1422
+ sx: atLeast(160)
1330
1423
  }),
1331
- /*#__PURE__*/ _jsx(TextField, {
1424
+ /*#__PURE__*/ _jsx(MemoTextField, {
1332
1425
  type: "number",
1333
1426
  size: "small",
1334
1427
  label: "Spent at least",
1335
1428
  helperText: "Lifetime, in your store's currency",
1336
1429
  value: draft.ltvAtLeast,
1337
- onChange: (event)=>set('ltvAtLeast', event.target.value),
1338
- sx: {
1339
- minWidth: 190
1340
- }
1430
+ onChange: on.ltvAtLeast,
1431
+ sx: atLeast(190)
1341
1432
  }),
1342
- /*#__PURE__*/ _jsx(TextField, {
1433
+ /*#__PURE__*/ _jsx(MemoTextField, {
1343
1434
  type: "number",
1344
1435
  size: "small",
1345
1436
  label: "Bought within (days)",
1346
1437
  value: draft.lastPurchaseWithinDays,
1347
- onChange: (event)=>set('lastPurchaseWithinDays', event.target.value),
1348
- sx: {
1349
- minWidth: 190
1350
- }
1438
+ onChange: on.lastPurchaseWithinDays,
1439
+ sx: atLeast(190)
1351
1440
  }),
1352
- /*#__PURE__*/ _jsx(TextField, {
1441
+ /*#__PURE__*/ _jsx(MemoTextField, {
1353
1442
  type: "number",
1354
1443
  size: "small",
1355
1444
  label: "Nothing bought for (days)",
1356
1445
  helperText: "Lapsed customers — never bought is not lapsed",
1357
1446
  value: draft.noPurchaseForDays,
1358
- onChange: (event)=>set('noPurchaseForDays', event.target.value),
1359
- sx: {
1360
- minWidth: 220
1361
- }
1447
+ onChange: on.noPurchaseForDays,
1448
+ sx: atLeast(220)
1362
1449
  })
1363
1450
  ]
1364
1451
  }),
@@ -1377,51 +1464,41 @@ export function DynamicListRuleFields(props) {
1377
1464
  direction: "row",
1378
1465
  spacing: 1,
1379
1466
  useFlexGap: true,
1380
- sx: {
1381
- flexWrap: 'wrap'
1382
- },
1467
+ sx: WRAP,
1383
1468
  children: [
1384
- /*#__PURE__*/ _jsx(TextField, {
1469
+ /*#__PURE__*/ _jsx(MemoTextField, {
1385
1470
  type: "number",
1386
1471
  size: "small",
1387
1472
  label: "Opened within (days)",
1388
1473
  value: draft.openedWithinDays,
1389
- onChange: (event)=>set('openedWithinDays', event.target.value),
1390
- sx: {
1391
- minWidth: 190
1392
- }
1474
+ onChange: on.openedWithinDays,
1475
+ sx: atLeast(190)
1393
1476
  }),
1394
- /*#__PURE__*/ _jsx(TextField, {
1477
+ /*#__PURE__*/ _jsx(MemoTextField, {
1395
1478
  type: "number",
1396
1479
  size: "small",
1397
1480
  label: "Clicked within (days)",
1398
1481
  value: draft.clickedWithinDays,
1399
- onChange: (event)=>set('clickedWithinDays', event.target.value),
1400
- sx: {
1401
- minWidth: 190
1402
- }
1482
+ onChange: on.clickedWithinDays,
1483
+ sx: atLeast(190)
1403
1484
  }),
1404
- /*#__PURE__*/ _jsx(TextField, {
1485
+ /*#__PURE__*/ _jsx(MemoTextField, {
1405
1486
  type: "number",
1406
1487
  size: "small",
1407
1488
  label: "Nothing opened for (days)",
1408
1489
  helperText: "Never opened counts",
1409
1490
  value: draft.notOpenedForDays,
1410
- onChange: (event)=>set('notOpenedForDays', event.target.value),
1411
- sx: {
1412
- minWidth: 210
1413
- }
1491
+ onChange: on.notOpenedForDays,
1492
+ sx: atLeast(210)
1414
1493
  }),
1415
- /*#__PURE__*/ _jsx(TextField, {
1494
+ /*#__PURE__*/ _jsx(MemoTextField, {
1416
1495
  type: "number",
1417
1496
  size: "small",
1418
1497
  label: "Nothing clicked for (days)",
1419
1498
  helperText: "Never clicked counts",
1420
1499
  value: draft.notClickedForDays,
1421
- onChange: (event)=>set('notClickedForDays', event.target.value),
1422
- sx: {
1423
- minWidth: 210
1424
- }
1500
+ onChange: on.notClickedForDays,
1501
+ sx: atLeast(210)
1425
1502
  })
1426
1503
  ]
1427
1504
  }),
@@ -1435,61 +1512,35 @@ export function DynamicListRuleFields(props) {
1435
1512
  direction: "row",
1436
1513
  spacing: 1,
1437
1514
  useFlexGap: true,
1438
- sx: {
1439
- flexWrap: 'wrap'
1440
- },
1515
+ sx: WRAP,
1441
1516
  children: [
1442
- /*#__PURE__*/ _jsx(TextField, {
1517
+ /*#__PURE__*/ _jsx(MemoTextField, {
1443
1518
  select: true,
1444
1519
  size: "small",
1445
1520
  label: "Already on",
1446
1521
  value: draft.inListIds,
1447
- onChange: (event)=>set('inListIds', asArray(event.target.value)),
1448
- slotProps: {
1449
- select: {
1450
- multiple: true
1451
- }
1452
- },
1522
+ onChange: on.inListIds,
1523
+ slotProps: MULTIPLE,
1453
1524
  helperText: "Members of every audience picked",
1454
- sx: {
1455
- minWidth: 220
1456
- },
1457
- children: lists.map((row)=>{
1458
- var _row_name;
1459
- return /*#__PURE__*/ _jsx(MenuItem, {
1460
- value: row.$id,
1461
- children: (_row_name = row.name) != null ? _row_name : row.$id
1462
- }, row.$id);
1463
- })
1525
+ sx: atLeast(220),
1526
+ children: listMenu
1464
1527
  }),
1465
- /*#__PURE__*/ _jsx(TextField, {
1528
+ /*#__PURE__*/ _jsx(MemoTextField, {
1466
1529
  select: true,
1467
1530
  size: "small",
1468
1531
  label: "Not on",
1469
1532
  value: draft.notInListIds,
1470
- onChange: (event)=>set('notInListIds', asArray(event.target.value)),
1471
- slotProps: {
1472
- select: {
1473
- multiple: true
1474
- }
1475
- },
1533
+ onChange: on.notInListIds,
1534
+ slotProps: MULTIPLE,
1476
1535
  helperText: "Excludes members of any audience picked",
1477
- sx: {
1478
- minWidth: 220
1479
- },
1480
- children: lists.map((row)=>{
1481
- var _row_name;
1482
- return /*#__PURE__*/ _jsx(MenuItem, {
1483
- value: row.$id,
1484
- children: (_row_name = row.name) != null ? _row_name : row.$id
1485
- }, row.$id);
1486
- })
1536
+ sx: atLeast(220),
1537
+ children: listMenu
1487
1538
  })
1488
1539
  ]
1489
1540
  })
1490
1541
  ]
1491
1542
  });
1492
- }
1543
+ });
1493
1544
  DynamicListRuleFields.displayName = 'DynamicListRuleFields';
1494
1545
  /**
1495
1546
  * A draft seeded from a rule that arrives after the form does.