@clyrex-digital/clyrex-controls-dev 0.8.1-dev.20260924103055 → 0.8.1-dev.20260925110206

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.js CHANGED
@@ -5686,7 +5686,7 @@ var DataBindingControls_exports = {};
5686
5686
  __export(DataBindingControls_exports, {
5687
5687
  default: () => DataBindingControls_default
5688
5688
  });
5689
- var import_react59, import_navigation, import_jsx_runtime86, ACTIVE_QUERY_CONTROLS_EVENT, humanize, escapeODataString, parseFacetSelections, buildFacetFilter, parseODataSearch, SearchIcon, ResetIcon, normalizeSortOption, DataBindingControls, DataBindingControls_default;
5689
+ var import_react59, import_navigation, import_jsx_runtime86, humanize, escapeODataString, parseFacetSelections, buildFacetFilter, parseODataSearch, SearchIcon, ResetIcon, FilterSlidersIcon, ChevronDownIcon, CheckIcon, CloseIcon, normalizeSortOption, DataBindingControls, DataBindingControls_default;
5690
5690
  var init_DataBindingControls = __esm({
5691
5691
  "src/components/pageRenderingEngine/nodes/DataBindingControls.tsx"() {
5692
5692
  "use strict";
@@ -5698,22 +5698,15 @@ var init_DataBindingControls = __esm({
5698
5698
  init_InputControlType();
5699
5699
  init_StyleTypes();
5700
5700
  import_jsx_runtime86 = require("react/jsx-runtime");
5701
- ACTIVE_QUERY_CONTROLS_EVENT = "data-binding-active-query-controls";
5702
5701
  humanize = (value) => value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
5703
5702
  escapeODataString = (value) => value.replace(/'/g, "''");
5704
5703
  parseFacetSelections = (filter) => {
5705
5704
  const selections = /* @__PURE__ */ new Map();
5706
- const groupedExpression = /\(\s*([^()]+?)\s+eq\s+'((?:''|[^'])*)'\s*\)/gi;
5707
- for (const match of filter.matchAll(groupedExpression)) {
5708
- selections.set(match[1].trim(), match[2].replace(/''/g, "'"));
5709
- }
5710
- if (selections.size === 0) {
5711
- const ungroupedExpression = /(?:^|\s+and\s+)\s*([^()]+?)\s+eq\s+'((?:''|[^'])*)'\s*(?=\s+and\s+|$)/gi;
5712
- for (const match of filter.matchAll(ungroupedExpression)) {
5713
- selections.set(
5714
- match[1].trim(),
5715
- match[2].replace(/''/g, "'")
5716
- );
5705
+ if (!filter || !filter.trim()) return selections;
5706
+ const expression = /(?:^|[\s()]+(?:and[\s()]+)?)([\w./-]+)\s+eq\s+'((?:''|[^'])*)'/gi;
5707
+ for (const match of filter.matchAll(expression)) {
5708
+ if (match[1] && match[2] !== void 0) {
5709
+ selections.set(match[1].trim(), match[2].replace(/''/g, "'"));
5717
5710
  }
5718
5711
  }
5719
5712
  return selections;
@@ -5764,10 +5757,64 @@ var init_DataBindingControls = __esm({
5764
5757
  ]
5765
5758
  }
5766
5759
  );
5760
+ FilterSlidersIcon = ({ className = "" }) => /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)(
5761
+ "svg",
5762
+ {
5763
+ "aria-hidden": "true",
5764
+ className,
5765
+ fill: "none",
5766
+ viewBox: "0 0 24 24",
5767
+ stroke: "currentColor",
5768
+ strokeWidth: "2",
5769
+ children: [
5770
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 4h18M3 12h18M3 20h18" }),
5771
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("circle", { cx: "8", cy: "4", r: "2", fill: "currentColor" }),
5772
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("circle", { cx: "16", cy: "12", r: "2", fill: "currentColor" }),
5773
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("circle", { cx: "10", cy: "20", r: "2", fill: "currentColor" })
5774
+ ]
5775
+ }
5776
+ );
5777
+ ChevronDownIcon = ({ className = "" }) => /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
5778
+ "svg",
5779
+ {
5780
+ "aria-hidden": "true",
5781
+ className,
5782
+ fill: "none",
5783
+ viewBox: "0 0 24 24",
5784
+ stroke: "currentColor",
5785
+ strokeWidth: "2",
5786
+ children: /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "m19 9-7 7-7-7" })
5787
+ }
5788
+ );
5789
+ CheckIcon = ({ className = "" }) => /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
5790
+ "svg",
5791
+ {
5792
+ "aria-hidden": "true",
5793
+ className,
5794
+ fill: "none",
5795
+ viewBox: "0 0 24 24",
5796
+ stroke: "currentColor",
5797
+ strokeWidth: "2.5",
5798
+ children: /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" })
5799
+ }
5800
+ );
5801
+ CloseIcon = ({ className = "" }) => /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
5802
+ "svg",
5803
+ {
5804
+ "aria-hidden": "true",
5805
+ className,
5806
+ fill: "none",
5807
+ viewBox: "0 0 24 24",
5808
+ stroke: "currentColor",
5809
+ strokeWidth: "2",
5810
+ children: /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" })
5811
+ }
5812
+ );
5767
5813
  normalizeSortOption = (option) => {
5768
5814
  const direction = option.direction.trim();
5815
+ const title = option.title || humanize(option.name);
5769
5816
  return {
5770
- label: option.title || humanize(option.name),
5817
+ label: title,
5771
5818
  value: [option.name, direction].filter(Boolean).join(" ")
5772
5819
  };
5773
5820
  };
@@ -5782,59 +5829,59 @@ var init_DataBindingControls = __esm({
5782
5829
  mode = "toolbar"
5783
5830
  }) => {
5784
5831
  const router = (0, import_navigation.useRouter)();
5785
- const facetGroups = Object.entries(facets ?? {}).filter(
5786
- ([, values]) => Array.isArray(values) && values.length > 0
5832
+ const searchParams = (0, import_navigation.useSearchParams)();
5833
+ const [collapsedCategories, setCollapsedCategories] = (0, import_react59.useState)({});
5834
+ const [isSortOpen, setIsSortOpen] = (0, import_react59.useState)(false);
5835
+ const sortDropdownRef = (0, import_react59.useRef)(null);
5836
+ (0, import_react59.useEffect)(() => {
5837
+ const handleClickOutside = (event) => {
5838
+ if (sortDropdownRef.current && !sortDropdownRef.current.contains(event.target)) {
5839
+ setIsSortOpen(false);
5840
+ }
5841
+ };
5842
+ document.addEventListener("mousedown", handleClickOutside);
5843
+ return () => document.removeEventListener("mousedown", handleClickOutside);
5844
+ }, []);
5845
+ const toggleCategory = (field) => {
5846
+ setCollapsedCategories((prev) => ({
5847
+ ...prev,
5848
+ [field]: !prev[field]
5849
+ }));
5850
+ };
5851
+ const searchParamsFilter = searchParams?.get("$filter") ?? searchParams?.get("filter");
5852
+ const searchParamsSort = searchParams?.get("$orderby") ?? searchParams?.get("orderBy");
5853
+ const searchParamsSearch = searchParams?.get("$search");
5854
+ const currentFilter = searchParamsFilter !== void 0 && searchParamsFilter !== null ? searchParamsFilter : query?.["$filter"] ?? query?.filter ?? "";
5855
+ const currentSort = searchParamsSort !== void 0 && searchParamsSort !== null ? searchParamsSort : query?.["$orderby"] ?? query?.orderBy ?? "";
5856
+ const currentSearch = parseODataSearch(
5857
+ searchParamsSearch !== void 0 && searchParamsSearch !== null ? searchParamsSearch : query?.["$search"]
5787
5858
  );
5788
- const normalizedSortOptions = (sortOptions ?? []).filter((option) => option?.name && option?.direction).map(normalizeSortOption).filter((option) => option.value);
5789
- const queryFilter = query?.["$filter"] ?? query?.filter ?? "";
5790
- const querySort = query?.["$orderby"] ?? query?.orderBy ?? "";
5791
- const [currentFilter, setCurrentFilter] = (0, import_react59.useState)(queryFilter);
5792
- const [currentSort, setCurrentSort] = (0, import_react59.useState)(querySort);
5793
- const selectedFacetValues = parseFacetSelections(currentFilter);
5794
- const currentSearch = parseODataSearch(query?.["$search"]);
5795
5859
  const [searchText, setSearchText] = (0, import_react59.useState)(currentSearch);
5796
- const lastNavigationUrl = (0, import_react59.useRef)(null);
5797
5860
  (0, import_react59.useEffect)(() => {
5798
5861
  setSearchText(currentSearch);
5799
5862
  }, [currentSearch]);
5800
- (0, import_react59.useEffect)(() => {
5801
- setCurrentFilter(queryFilter);
5802
- }, [queryFilter]);
5803
- (0, import_react59.useEffect)(() => {
5804
- setCurrentSort(querySort);
5805
- }, [querySort]);
5806
- (0, import_react59.useEffect)(() => {
5807
- const syncActiveQueryControls2 = (event) => {
5808
- const { filter, sort } = event.detail;
5809
- if (filter !== void 0) {
5810
- setCurrentFilter(filter);
5811
- }
5812
- if (sort !== void 0) {
5813
- setCurrentSort(sort);
5814
- }
5815
- };
5816
- window.addEventListener(
5817
- ACTIVE_QUERY_CONTROLS_EVENT,
5818
- syncActiveQueryControls2
5819
- );
5820
- return () => window.removeEventListener(
5821
- ACTIVE_QUERY_CONTROLS_EVENT,
5822
- syncActiveQueryControls2
5823
- );
5824
- }, []);
5825
- const syncActiveQueryControls = (0, import_react59.useCallback)(
5826
- (updates) => {
5827
- window.dispatchEvent(
5828
- new CustomEvent(ACTIVE_QUERY_CONTROLS_EVENT, {
5829
- detail: updates
5830
- })
5831
- );
5832
- },
5833
- []
5863
+ const selectedFacetValues = (0, import_react59.useMemo)(
5864
+ () => parseFacetSelections(currentFilter),
5865
+ [currentFilter]
5834
5866
  );
5867
+ const activeFacets = (0, import_react59.useMemo)(() => {
5868
+ if (!facets) return [];
5869
+ return Object.entries(facets).map(([field, values]) => {
5870
+ if (!Array.isArray(values)) return null;
5871
+ const filteredValues = values.filter(
5872
+ (item) => item.count > 0 || selectedFacetValues.get(field) === item.value
5873
+ );
5874
+ if (filteredValues.length === 0) return null;
5875
+ return [field, filteredValues];
5876
+ }).filter((group) => group !== null);
5877
+ }, [facets, selectedFacetValues]);
5878
+ const normalizedSortOptions = (sortOptions ?? []).filter((option) => option?.name && option?.direction).map(normalizeSortOption).filter((option) => option.value);
5879
+ const selectedSortOption = normalizedSortOptions.find((opt) => opt.value === currentSort) || normalizedSortOptions[0];
5880
+ const selectedSortLabel = selectedSortOption?.label || "";
5835
5881
  const getQueryUrl = (0, import_react59.useCallback)(
5836
5882
  (updates) => {
5837
- const params = new URLSearchParams(query ?? {});
5883
+ const initialParams = searchParams ? searchParams.toString() : "";
5884
+ const params = new URLSearchParams(initialParams || query || {});
5838
5885
  Object.entries(updates).forEach(([key, value]) => {
5839
5886
  if (value) {
5840
5887
  params.set(key, value);
@@ -5849,38 +5896,48 @@ var init_DataBindingControls = __esm({
5849
5896
  ).join("&");
5850
5897
  return queryString ? `${path}?${queryString}` : path;
5851
5898
  },
5852
- [path, query]
5899
+ [path, query, searchParams]
5853
5900
  );
5854
- const getFacetUrl = (field, value) => {
5855
- const updatedSelections = new Map(selectedFacetValues);
5856
- if (value) {
5857
- updatedSelections.set(field, value);
5858
- } else {
5859
- updatedSelections.delete(field);
5860
- }
5861
- const updatedFilter = buildFacetFilter(updatedSelections);
5862
- syncActiveQueryControls({ filter: updatedFilter });
5863
- return getQueryUrl({
5864
- $filter: updatedFilter || void 0,
5865
- filter: void 0
5866
- });
5867
- };
5868
5901
  const navigateWithoutReload = (0, import_react59.useCallback)(
5869
5902
  (url) => {
5870
- if (lastNavigationUrl.current === url) {
5871
- return;
5872
- }
5873
5903
  if (typeof window !== "undefined") {
5874
5904
  const targetUrl = new URL(url, window.location.origin);
5875
5905
  if (targetUrl.pathname === window.location.pathname && targetUrl.search === window.location.search) {
5876
5906
  return;
5877
5907
  }
5878
5908
  }
5879
- lastNavigationUrl.current = url;
5880
5909
  router.push(url, { scroll: false });
5881
5910
  },
5882
5911
  [router]
5883
5912
  );
5913
+ const getFacetUrl = (field, value) => {
5914
+ const updatedSelections = new Map(selectedFacetValues);
5915
+ if (value) {
5916
+ if (updatedSelections.get(field) === value) {
5917
+ updatedSelections.delete(field);
5918
+ } else {
5919
+ updatedSelections.set(field, value);
5920
+ }
5921
+ } else {
5922
+ updatedSelections.delete(field);
5923
+ }
5924
+ const updatedFilter = buildFacetFilter(updatedSelections);
5925
+ return getQueryUrl({
5926
+ $filter: updatedFilter || void 0,
5927
+ filter: void 0
5928
+ });
5929
+ };
5930
+ const clearFacetField = (field) => {
5931
+ const updatedSelections = new Map(selectedFacetValues);
5932
+ updatedSelections.delete(field);
5933
+ const updatedFilter = buildFacetFilter(updatedSelections);
5934
+ navigateWithoutReload(
5935
+ getQueryUrl({
5936
+ $filter: updatedFilter || void 0,
5937
+ filter: void 0
5938
+ })
5939
+ );
5940
+ };
5884
5941
  const handleSearch = (0, import_react59.useCallback)(async () => {
5885
5942
  const trimmedSearchText = searchText.trim();
5886
5943
  if (trimmedSearchText === currentSearch.trim()) {
@@ -5894,7 +5951,6 @@ var init_DataBindingControls = __esm({
5894
5951
  return { isSuccessful: true };
5895
5952
  }, [currentSearch, getQueryUrl, navigateWithoutReload, searchText]);
5896
5953
  const handleClearAll = (0, import_react59.useCallback)(async () => {
5897
- syncActiveQueryControls({ filter: "", sort: "" });
5898
5954
  navigateWithoutReload(
5899
5955
  getQueryUrl({
5900
5956
  $filter: void 0,
@@ -5904,67 +5960,69 @@ var init_DataBindingControls = __esm({
5904
5960
  })
5905
5961
  );
5906
5962
  return { isSuccessful: true };
5907
- }, [getQueryUrl, navigateWithoutReload, syncActiveQueryControls]);
5908
- const hasFacetControls = showFacets && facetGroups.length > 0;
5963
+ }, [getQueryUrl, navigateWithoutReload]);
5964
+ const hasFacetControls = showFacets && activeFacets.length > 0;
5909
5965
  const hasSortControls = showSortOptions && normalizedSortOptions.length > 0;
5910
5966
  const hasActiveFiltersOrSort = Boolean(
5911
5967
  currentFilter.trim() || currentSort.trim()
5912
5968
  );
5969
+ const selectedFacetCount = selectedFacetValues.size;
5913
5970
  const shouldShowToolbarClearAll = hasActiveFiltersOrSort && !hasFacetControls;
5914
5971
  const shouldShowToolbar = mode === "toolbar" && (showSearchBar || hasSortControls || shouldShowToolbarClearAll);
5915
5972
  const shouldShowFacets = mode === "facets" && hasFacetControls;
5916
5973
  if (!shouldShowToolbar && !shouldShowFacets) {
5917
5974
  return null;
5918
5975
  }
5919
- const renderFacetLinks = (field, values, stacked = false) => /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: stacked ? "flex flex-col gap-0.5" : "flex flex-wrap gap-1.5", children: [
5920
- /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
5976
+ const renderFacetItems = (field, values) => /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("div", { className: "flex flex-col gap-1 pt-1 pb-1", children: values.map((facet) => {
5977
+ const isSelected = selectedFacetValues.get(field) === facet.value;
5978
+ return /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)(
5921
5979
  "button",
5922
5980
  {
5923
5981
  type: "button",
5924
- onClick: () => navigateWithoutReload(getFacetUrl(field)),
5925
- "aria-pressed": !selectedFacetValues.has(field),
5926
- className: `cursor-pointer px-3 py-1.5 text-left text-sm transition-all duration-150 ease-out hover:shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary ${stacked ? "w-full" : "border"} ${selectedFacetValues.has(field) ? "hover:bg-info-soft" : "bg-primary-base font-semibold hover:brightness-95"}`,
5927
- children: "All"
5928
- }
5929
- ),
5930
- values.map((facet) => {
5931
- const isSelected = selectedFacetValues.get(field) === facet.value;
5932
- return /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)(
5933
- "button",
5934
- {
5935
- type: "button",
5936
- onClick: () => navigateWithoutReload(getFacetUrl(field, facet.value)),
5937
- "aria-pressed": isSelected,
5938
- className: `cursor-pointer px-3 py-1.5 text-left text-sm transition-all duration-150 ease-out hover:shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary ${stacked ? "w-full" : "border"} ${isSelected ? "bg-primary-base font-semibold hover:brightness-95" : "hover:bg-info-soft"}`,
5939
- children: [
5940
- facet.value,
5941
- " (",
5942
- facet.count,
5943
- ")"
5944
- ]
5945
- },
5946
- `${field}-${facet.value}`
5947
- );
5948
- })
5949
- ] });
5982
+ onClick: () => navigateWithoutReload(getFacetUrl(field, facet.value)),
5983
+ "aria-pressed": isSelected,
5984
+ className: `group flex w-full cursor-pointer items-center justify-between gap-2.5 rounded-md px-2 py-1.5 text-left text-sm transition-all duration-150 ease-out focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary-base ${isSelected ? "bg-neutral-soft font-medium border " : "hover:bg-neutral-weak border border-transparent"}`,
5985
+ children: [
5986
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: "flex min-w-0 items-center gap-2.5", children: [
5987
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
5988
+ "span",
5989
+ {
5990
+ className: `flex h-4 w-4 shrink-0 items-center justify-center rounded-[3px] border transition-colors duration-150 ${isSelected ? "bg-primary-base border-primary-base" : " group-hover:border-neutral-strong bg-default"}`,
5991
+ children: isSelected && /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(CheckIcon, { className: "h-3 w-3" })
5992
+ }
5993
+ ),
5994
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("span", { className: "leading-snug break-words", children: facet.value })
5995
+ ] }),
5996
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
5997
+ "span",
5998
+ {
5999
+ className: `shrink-0 rounded px-1.5 py-0.5 text-xs transition-colors ml-2 ${isSelected ? "bg-neutral-weak font-semibold" : "bg-neutral-weak font-normal"}`,
6000
+ children: facet.count
6001
+ }
6002
+ )
6003
+ ]
6004
+ },
6005
+ `${field}-${facet.value}`
6006
+ );
6007
+ }) });
5950
6008
  return /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)(
5951
6009
  "div",
5952
6010
  {
5953
- className: mode === "toolbar" ? "my-6" : "flex flex-col gap-2 border bg-default rounded-[5px] p-3",
6011
+ className: mode === "toolbar" ? "my-6" : "flex flex-col rounded-md border bg-default p-3.5 shadow-sm",
5954
6012
  children: [
5955
- shouldShowToolbar && /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: "flex flex-col gap-3 md:flex-row md:items-center md:justify-between", children: [
6013
+ shouldShowToolbar && /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between", children: [
5956
6014
  showSearchBar && /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)(
5957
6015
  "form",
5958
6016
  {
5959
6017
  role: "search",
5960
- className: "flex w-full flex-col gap-3 rounded-xl border border-info bg-info-soft p-3 sm:flex-row sm:items-center md:min-w-0 md:flex-1",
6018
+ className: "flex w-full items-center gap-2 sm:flex-row md:min-w-0 md:flex-1",
5961
6019
  onSubmit: (event) => {
5962
6020
  event.preventDefault();
5963
6021
  void handleSearch();
5964
6022
  },
5965
6023
  children: [
5966
- /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: "relative bg-default rounded-xl min-w-0 flex-1", children: [
5967
- /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(SearchIcon, { className: "pointer-events-none absolute left-4 top-1/2 z-10 h-5 w-5 -translate-y-1/2" }),
6024
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: "relative min-w-0 flex-1", children: [
6025
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(SearchIcon, { className: "pointer-events-none absolute left-3.5 top-1/2 z-10 h-4 w-4 -translate-y-1/2 opacity-60" }),
5968
6026
  /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
5969
6027
  InputControl_default,
5970
6028
  {
@@ -5975,7 +6033,7 @@ var init_DataBindingControls = __esm({
5975
6033
  attributes: {
5976
6034
  placeholder: "Search discussions, topics, or keywords..."
5977
6035
  },
5978
- inputClasses: "!mt-0 !rounded-xl !py-3 !pl-11 !pr-4 !shadow-sm"
6036
+ inputClasses: "!mt-0 !h-10 !rounded-md !border-neutral-soft !bg-default !py-2 !pl-10 !pr-4 !shadow-sm !text-sm focus:!border-primary-base focus:!ring-primary-base"
5979
6037
  }
5980
6038
  )
5981
6039
  ] }),
@@ -5984,9 +6042,9 @@ var init_DataBindingControls = __esm({
5984
6042
  {
5985
6043
  ButtonType: "Primary" /* Solid */,
5986
6044
  onClick: handleSearch,
5987
- className: "!rounded-xl !px-6 !py-3 sm:self-stretch",
6045
+ className: "!h-10 !rounded-md !px-5 !py-0 flex items-center justify-center gap-1.5 shrink-0",
5988
6046
  children: [
5989
- /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(SearchIcon, { className: "h-5 w-5" }),
6047
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(SearchIcon, { className: "h-4 w-4" }),
5990
6048
  /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("span", { children: "Search" })
5991
6049
  ]
5992
6050
  }
@@ -5994,61 +6052,124 @@ var init_DataBindingControls = __esm({
5994
6052
  ]
5995
6053
  }
5996
6054
  ),
5997
- hasSortControls && /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: "flex w-full items-center gap-3 rounded-2xl border border-info bg-info-soft p-3 text-sm md:ml-auto md:w-auto md:min-w-72 md:self-stretch", children: [
5998
- /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
5999
- "label",
6055
+ hasSortControls && /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: "relative shrink-0", ref: sortDropdownRef, children: [
6056
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)(
6057
+ "button",
6000
6058
  {
6001
- htmlFor: "sortOptions",
6002
- className: "shrink-0 whitespace-nowrap font-medium",
6003
- children: "Sort by"
6059
+ type: "button",
6060
+ onClick: () => setIsSortOpen((prev) => !prev),
6061
+ "aria-expanded": isSortOpen,
6062
+ className: "inline-flex h-10 items-center justify-between gap-2.5 rounded-md border bg-default px-3.5 text-sm font-normal shadow-sm transition-all hover:bg-neutral-weak focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-base cursor-pointer whitespace-nowrap",
6063
+ children: [
6064
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("span", { className: "whitespace-nowrap", children: [
6065
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("span", { className: "opacity-70", children: "Sort by: " }),
6066
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("span", { className: "font-semibold", children: selectedSortLabel })
6067
+ ] }),
6068
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
6069
+ ChevronDownIcon,
6070
+ {
6071
+ className: `h-4 w-4 shrink-0 transition-transform duration-200 ${isSortOpen ? "rotate-180" : ""}`
6072
+ }
6073
+ )
6074
+ ]
6004
6075
  }
6005
6076
  ),
6006
- /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("div", { className: "min-w-0 flex-1 rounded-xl bg-default md:min-w-48", children: /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
6007
- InputControl_default,
6008
- {
6009
- name: "sortOptions",
6010
- controlType: InputControlType_default.select,
6011
- value: currentSort,
6012
- dataset: normalizedSortOptions,
6013
- dataKeyFieldName: "value",
6014
- dataTextFieldName: "label",
6015
- callback: (updatedValues) => {
6016
- const updatedSort = updatedValues.value || "";
6017
- syncActiveQueryControls({ sort: updatedSort });
6018
- navigateWithoutReload(
6019
- getQueryUrl({
6020
- $orderby: updatedSort || void 0,
6021
- orderBy: void 0
6022
- })
6023
- );
6077
+ isSortOpen && /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("div", { className: "absolute right-0 z-50 mt-1.5 w-max min-w-full max-h-80 overflow-y-auto rounded-lg border border-neutral-soft bg-default p-1 shadow-lg", children: normalizedSortOptions.map((option) => {
6078
+ const isSelected = currentSort ? option.value === currentSort : option.value === normalizedSortOptions[0]?.value;
6079
+ return /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)(
6080
+ "button",
6081
+ {
6082
+ type: "button",
6083
+ onClick: () => {
6084
+ setIsSortOpen(false);
6085
+ navigateWithoutReload(
6086
+ getQueryUrl({
6087
+ $orderby: option.value || void 0,
6088
+ orderBy: void 0
6089
+ })
6090
+ );
6091
+ },
6092
+ className: `flex w-full items-center justify-between gap-4 rounded-md px-3 py-1.5 text-left text-sm transition-colors cursor-pointer whitespace-nowrap ${isSelected ? "bg-primary-base font-semibold" : "hover:bg-neutral-soft font-normal"}`,
6093
+ children: [
6094
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("span", { className: "whitespace-nowrap", children: option.label }),
6095
+ isSelected && /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(CheckIcon, { className: "h-4 w-4 shrink-0" })
6096
+ ]
6024
6097
  },
6025
- attributes: { placeholder: "Default" },
6026
- inputClasses: "!mt-0 !rounded-xl !border-info !bg-default !px-4 !py-3 !text-body !shadow-sm focus:!border-primary focus:!ring-primary-base"
6027
- }
6028
- ) })
6098
+ option.value
6099
+ );
6100
+ }) })
6029
6101
  ] })
6030
6102
  ] }),
6031
- shouldShowFacets && /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: "flex items-center justify-between gap-2 border-b pb-2", children: [
6032
- /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("h2", { className: "text-lg font-semibold", children: "Filters" }),
6033
- hasActiveFiltersOrSort && /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)(
6103
+ shouldShowFacets && /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: "flex flex-col gap-3", children: [
6104
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: "flex items-center justify-between border-b pb-3", children: [
6105
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: "flex items-center gap-2", children: [
6106
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(FilterSlidersIcon, { className: "h-4 w-4" }),
6107
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("h2", { className: "text-base font-semibold tracking-tight", children: "Filters" }),
6108
+ selectedFacetCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("span", { className: "flex h-5 items-center justify-center rounded-full bg-primary-base px-2 text-xs font-semibold", children: selectedFacetCount })
6109
+ ] }),
6110
+ hasActiveFiltersOrSort && /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)(
6111
+ "button",
6112
+ {
6113
+ type: "button",
6114
+ onClick: () => void handleClearAll(),
6115
+ className: "group inline-flex cursor-pointer items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium transition-colors duration-150 hover:bg-neutral-soft focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary-base",
6116
+ "aria-label": "Clear all filters and sorting",
6117
+ children: [
6118
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(ResetIcon, { className: "h-3.5 w-3.5 transition-transform duration-200 group-hover:-rotate-45" }),
6119
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("span", { children: "Clear all" })
6120
+ ]
6121
+ }
6122
+ )
6123
+ ] }),
6124
+ selectedFacetCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("div", { className: "flex flex-wrap gap-1.5 pb-2.5 border-b ", children: Array.from(selectedFacetValues.entries()).map(([field, value]) => /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)(
6034
6125
  "button",
6035
6126
  {
6036
6127
  type: "button",
6037
- onClick: () => void handleClearAll(),
6038
- className: "group inline-flex cursor-pointer items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium text-primary transition-colors duration-150 hover:bg-info-soft hover:text-primary-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",
6039
- "aria-label": "Clear all filters and sorting",
6128
+ onClick: () => clearFacetField(field),
6129
+ className: "group inline-flex max-w-full cursor-pointer items-center gap-1.5 rounded-md border bg-neutral-weak px-2.5 py-1 text-xs transition-colors hover:bg-neutral-soft",
6130
+ title: `Remove filter: ${value}`,
6040
6131
  children: [
6041
- /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(ResetIcon, { className: "h-3.5 w-3.5 transition-transform duration-200 group-hover:-rotate-45" }),
6042
- /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("span", { children: "Clear all" })
6132
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("span", { className: "font-medium truncate max-w-[150px]", children: value }),
6133
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(CloseIcon, { className: "h-3.5 w-3.5 shrink-0 opacity-60 group-hover:opacity-100" })
6043
6134
  ]
6044
- }
6045
- )
6046
- ] }),
6047
- shouldShowFacets && facetGroups.length === 1 && /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("nav", { "aria-label": `${humanize(facetGroups[0][0])} filters`, children: renderFacetLinks(facetGroups[0][0], facetGroups[0][1]) }),
6048
- shouldShowFacets && facetGroups.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("div", { className: "flex flex-col", children: facetGroups.map(([field, values]) => /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("details", { className: "border-b py-1 last:border-b-0", open: true, children: [
6049
- /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("summary", { className: "cursor-pointer rounded-md px-2 py-1.5 font-semibold transition-colors duration-150 hover:bg-info-soft focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary", children: humanize(field) }),
6050
- /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("div", { className: "px-1 pb-1 pt-0.5", children: renderFacetLinks(field, values, true) })
6051
- ] }, field)) })
6135
+ },
6136
+ `chip-${field}`
6137
+ )) }),
6138
+ activeFacets.length === 1 && /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("nav", { "aria-label": `${humanize(activeFacets[0][0])} filters`, children: /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)("div", { className: "py-1", children: [
6139
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("div", { className: "px-1 pb-2 font-medium text-sm", children: humanize(activeFacets[0][0]) }),
6140
+ renderFacetItems(activeFacets[0][0], activeFacets[0][1])
6141
+ ] }) }),
6142
+ activeFacets.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("div", { className: "flex flex-col divide-y divide-neutral-soft", children: activeFacets.map(([field, values]) => {
6143
+ const isOpen = !collapsedCategories[field];
6144
+ return /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)(
6145
+ "div",
6146
+ {
6147
+ className: "py-2 first:pt-0 last:pb-0",
6148
+ children: [
6149
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsxs)(
6150
+ "button",
6151
+ {
6152
+ type: "button",
6153
+ onClick: () => toggleCategory(field),
6154
+ className: "flex w-full cursor-pointer items-center justify-between rounded-md px-1.5 py-1.5 text-sm font-semibold transition-colors duration-150 hover:bg-neutral-weak focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary-base",
6155
+ children: [
6156
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("span", { children: humanize(field) }),
6157
+ /* @__PURE__ */ (0, import_jsx_runtime86.jsx)(
6158
+ ChevronDownIcon,
6159
+ {
6160
+ className: `h-4 w-4 transition-transform duration-200 ease-out ${isOpen ? "rotate-180" : ""}`
6161
+ }
6162
+ )
6163
+ ]
6164
+ }
6165
+ ),
6166
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime86.jsx)("div", { className: "pt-1", children: renderFacetItems(field, values) })
6167
+ ]
6168
+ },
6169
+ field
6170
+ );
6171
+ }) })
6172
+ ] })
6052
6173
  ]
6053
6174
  }
6054
6175
  );
@@ -9420,7 +9541,7 @@ var DivContainer = async (props) => {
9420
9541
  renderedContainer,
9421
9542
  paginationControl
9422
9543
  ] }),
9423
- dataBindingProperties?.showFacets && /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("aside", { className: " lg:sticky lg:top-[90px] lg:self-start", children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)(
9544
+ dataBindingProperties?.showFacets && /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("aside", { children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)(
9424
9545
  DataBindingControls2,
9425
9546
  {
9426
9547
  mode: "facets",
package/dist/index.mjs CHANGED
@@ -2420,7 +2420,7 @@ var GoogleMapNode_default = GoogleMapNode;
2420
2420
  // src/components/pageRenderingEngine/nodes/DivContainer.tsx
2421
2421
  import { jsx as jsx37, jsxs as jsxs15 } from "react/jsx-runtime";
2422
2422
  var Pagination = dynamic13(() => import("./Pagination-STOHS3LO.mjs"), { ssr: true });
2423
- var DataBindingControls = dynamic13(() => import("./DataBindingControls-SLNZLI3I.mjs"), {
2423
+ var DataBindingControls = dynamic13(() => import("./DataBindingControls-6IS5F3QX.mjs"), {
2424
2424
  ssr: true
2425
2425
  });
2426
2426
  var Slider = dynamic13(() => import("./Slider-42T6THEQ.mjs"), {
@@ -2961,7 +2961,7 @@ var DivContainer = async (props) => {
2961
2961
  renderedContainer,
2962
2962
  paginationControl
2963
2963
  ] }),
2964
- dataBindingProperties?.showFacets && /* @__PURE__ */ jsx37("aside", { className: " lg:sticky lg:top-[90px] lg:self-start", children: /* @__PURE__ */ jsx37(
2964
+ dataBindingProperties?.showFacets && /* @__PURE__ */ jsx37("aside", { children: /* @__PURE__ */ jsx37(
2965
2965
  DataBindingControls,
2966
2966
  {
2967
2967
  mode: "facets",