@phila/phila-ui-filter-summary 0.1.0-beta.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/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # FilterSummary Component
2
+
3
+ <!-- status-badge-start -->
4
+
5
+ ![Status: In Progress](https://img.shields.io/badge/-In%20Progress-yellow)
6
+
7
+ <!-- status-badge-end -->
8
+
9
+ `@phila/phila-ui-filter-summary` — filter summary for applied filters: a "Showing N results for …" line plus removable filter pills (one per active selection) and a Clear all action.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pnpm add @phila/phila-ui-filter-summary
15
+ ```
16
+
17
+ ## Shared model
18
+
19
+ `FilterSummary` uses the same `FilterDefinition[]` and `FilterValues` types as `FilterChipGroup` and `FilterPanel`. Bind all three to the same `ref<FilterValues>` and they stay in sync: removing a pill or clicking Clear all emits an updated model; Clear all preserves any ordering filters (those with `excludeFromCount: true`, e.g. Sort) so the user's sort preference is not lost.
20
+
21
+ ## Usage
22
+
23
+ ```vue
24
+ <script setup lang="ts">
25
+ import { ref } from "vue";
26
+ import { FilterSummary } from "@phila/phila-ui-filter-summary";
27
+ import type { FilterDefinition, FilterValues } from "@phila/phila-ui-core";
28
+
29
+ const filters: FilterDefinition[] = [
30
+ /* ... */
31
+ ];
32
+ const values = ref<FilterValues>({ language: ["en", "es"] });
33
+ </script>
34
+
35
+ <template>
36
+ <FilterSummary :filters="filters" v-model="values" :results-count="21" query="health center" />
37
+ </template>
38
+ ```
39
+
40
+ ## Props
41
+
42
+ | Prop | Type | Default | Description |
43
+ | -------------- | -------------------- | ------------- | --------------------------------------------------------------------- |
44
+ | `filters` | `FilterDefinition[]` | — | Required. Same filter definitions used by the chip bar / panel. |
45
+ | `modelValue` | `FilterValues` | `{}` | Applied values (`v-model`). Removing a pill emits an updated copy. |
46
+ | `resultsCount` | `number` | — | When set, renders the "Showing N results…" line above the pills. |
47
+ | `query` | `string` | — | Searched term shown in the results line as `for "…"`. |
48
+ | `clearAllText` | `string` | `"Clear all"` | Label for the Clear all action (shown only when 2+ pills are active). |
49
+
50
+ ## Events
51
+
52
+ | Event | Payload | Description |
53
+ | ------------------- | -------------- | ------------------------------------------------------------------- |
54
+ | `update:modelValue` | `FilterValues` | Emitted when a pill is removed or Clear all is clicked (`v-model`). |
55
+
56
+ ## Slots
57
+
58
+ | Slot | Props | Description |
59
+ | ---------- | ---------------------------------- | ------------------------------------------------------------------------ |
60
+ | `#results` | `{ count: number, query: string }` | Replaces the default "Showing N results for …" line with custom content. |
61
+
62
+ ## Development
63
+
64
+ ### Install Dependencies
65
+
66
+ ```bash
67
+ pnpm install
68
+ ```
69
+
70
+ ### Build Library
71
+
72
+ ```bash
73
+ pnpm build
74
+ ```
75
+
76
+ ### Type Check
77
+
78
+ ```bash
79
+ pnpm type-check
80
+ ```
81
+
82
+ ## License
83
+
84
+ MIT
@@ -0,0 +1,30 @@
1
+ import { FilterValues } from '@phila/phila-ui-core';
2
+ import { FilterSummaryProps } from './index';
3
+ declare function __VLS_template(): {
4
+ attrs: Partial<{}>;
5
+ slots: {
6
+ results?(_: {
7
+ count: number | undefined;
8
+ query: string | undefined;
9
+ }): any;
10
+ };
11
+ refs: {};
12
+ rootEl: any;
13
+ };
14
+ type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
15
+ declare const __VLS_component: import('vue').DefineComponent<FilterSummaryProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
16
+ "update:modelValue": (value: FilterValues) => any;
17
+ }, string, import('vue').PublicProps, Readonly<FilterSummaryProps> & Readonly<{
18
+ "onUpdate:modelValue"?: ((value: FilterValues) => any) | undefined;
19
+ }>, {
20
+ modelValue: FilterValues;
21
+ clearAllText: string;
22
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
23
+ declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
24
+ export default _default;
25
+ type __VLS_WithTemplateSlots<T, S> = T & {
26
+ new (): {
27
+ $slots: S;
28
+ };
29
+ };
30
+ //# sourceMappingURL=FilterSummary.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterSummary.vue.d.ts","sourceRoot":"","sources":["../src/FilterSummary.vue"],"names":[],"mappings":"AAwKA,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AA8FlD,iBAAS,cAAc;WA8FT,OAAO,IAA6B;;;;;YAVrB,GAAG;;;;EAe/B;AAiBD,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AAC9D,QAAA,MAAM,eAAe;;;;;;;6EAQnB,CAAC;wBACkB,uBAAuB,CAAC,OAAO,eAAe,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAAnG,wBAAoG;AAapG,KAAK,uBAAuB,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IACxC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
package/dist/index.css ADDED
@@ -0,0 +1 @@
1
+ .phila-filter-summary[data-v-bcac6c42]{display:flex;flex-direction:column;gap:var(--spacing-xs, .5rem)}.phila-filter-summary__results[data-v-bcac6c42]{margin:0}.phila-filter-summary__query[data-v-bcac6c42]{font-style:italic;font-weight:700}.phila-filter-summary__pills[data-v-bcac6c42]{display:flex;flex-wrap:wrap;align-items:center;gap:var(--spacing-xs, .5rem)}.phila-filter-summary__clear-all[data-v-bcac6c42]{background:none;border:none;padding:0;margin-inline-start:var(--spacing-2xs, .25rem);cursor:pointer;font:inherit;color:var(--Schemes-Primary, #0f4d90);text-decoration:underline}.phila-filter-summary__clear-all[data-v-bcac6c42]:focus-visible{outline:2px solid currentColor;outline-offset:2px}
@@ -0,0 +1,15 @@
1
+ import { BaseProps, FilterDefinition, FilterValues } from '@phila/phila-ui-core';
2
+ export { default as FilterSummary } from './FilterSummary.vue';
3
+ export interface FilterSummaryProps extends BaseProps {
4
+ /** Same filter definitions used by the chip bar / panel. */
5
+ filters: FilterDefinition[];
6
+ /** Applied values (v-model). Removing a pill / Clear all emits an updated copy. */
7
+ modelValue?: FilterValues;
8
+ /** When set, renders the "Showing N results…" line. */
9
+ resultsCount?: number;
10
+ /** The searched term shown in the results line. */
11
+ query?: string;
12
+ /** Clear-all action label. */
13
+ clearAllText?: string;
14
+ }
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEtF,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE/D,MAAM,WAAW,kBAAmB,SAAQ,SAAS;IACnD,4DAA4D;IAC5D,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,mFAAmF;IACnF,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});require('./index.css');const e=require("vue"),C=require("@phila/phila-ui-core"),h=require("@phila/phila-ui-tags"),b={key:0,class:"phila-filter-summary__results"},g={class:"phila-filter-summary__query"},V={key:1,class:"phila-filter-summary__pills"},x=e.defineComponent({__name:"FilterSummary",props:{filters:{},modelValue:{default:()=>({})},resultsCount:{},query:{},clearAllText:{default:"Clear all"},className:{}},emits:["update:modelValue"],setup(a,{emit:i}){const s=a,n=i,u=e.computed(()=>s.filters.filter(l=>!l.excludeFromCount));function f(l,t){return l.choices?.find(r=>r.value===t)?.text??t}const c=e.computed(()=>{const l=[];for(const t of u.value){const r=s.modelValue[t.key];if(Array.isArray(r))for(const o of r){const m=f(t,o);l.push({filterKey:t.key,value:o,text:m,ariaLabel:`Remove ${t.label}: ${m}`})}else if(typeof r=="string"&&r){const o=f(t,r);l.push({filterKey:t.key,value:r,text:o,ariaLabel:`Remove ${t.label}: ${o}`})}else r===!0&&l.push({filterKey:t.key,value:!0,text:t.label,ariaLabel:`Remove ${t.label} filter`})}return l}),d=e.computed(()=>c.value.length>=2),k=e.computed(()=>{if(s.resultsCount==null)return"";const l=s.resultsCount===1?"result":"results";return`Showing ${s.resultsCount} ${l}`});function y(l){const t={};for(const r of Object.keys(l)){const o=l[r];t[r]=Array.isArray(o)?[...o]:o}return t}function v(l){const t=y(s.modelValue),r=t[l.filterKey];Array.isArray(r)?t[l.filterKey]=r.filter(o=>o!==l.value):typeof r=="string"?t[l.filterKey]="":r===!0&&(t[l.filterKey]=!1),n("update:modelValue",t)}function p(){const l=y(s.modelValue);for(const t of u.value)delete l[t.key];n("update:modelValue",l)}return(l,t)=>l.$slots.results||a.resultsCount!=null||c.value.length?(e.openBlock(),e.createElementBlock("div",{key:0,class:e.normalizeClass(e.unref(C.cn)("phila-filter-summary",s.className))},[l.$slots.results||a.resultsCount!=null?(e.openBlock(),e.createElementBlock("p",b,[e.renderSlot(l.$slots,"results",{count:a.resultsCount,query:a.query},()=>[e.createTextVNode(e.toDisplayString(k.value),1),a.query?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[t[0]||(t[0]=e.createTextVNode(" for ",-1)),e.createElementVNode("span",g,'"'+e.toDisplayString(a.query)+'"',1)],64)):e.createCommentVNode("",!0)],!0)])):e.createCommentVNode("",!0),c.value.length?(e.openBlock(),e.createElementBlock("div",V,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(c.value,(r,o)=>(e.openBlock(),e.createBlock(e.unref(h.Tags),{key:`${r.filterKey}:${String(r.value)}:${o}`,variant:"dismissible",color:"grey",text:r.text,"aria-label":r.ariaLabel,onDismiss:m=>v(r)},null,8,["text","aria-label","onDismiss"]))),128)),d.value?(e.openBlock(),e.createElementBlock("button",{key:0,type:"button",class:"phila-filter-summary__clear-all","aria-label":"Clear all filters",onClick:p},e.toDisplayString(a.clearAllText),1)):e.createCommentVNode("",!0)])):e.createCommentVNode("",!0)],2)):e.createCommentVNode("",!0)}}),B=(a,i)=>{const s=a.__vccOpts||a;for(const[n,u]of i)s[n]=u;return s},$=B(x,[["__scopeId","data-v-bcac6c42"]]);exports.FilterSummary=$;
package/dist/index.mjs ADDED
@@ -0,0 +1,107 @@
1
+ import { defineComponent as A, computed as m, createElementBlock as u, createCommentVNode as n, openBlock as o, normalizeClass as K, unref as b, renderSlot as _, createTextVNode as x, toDisplayString as v, Fragment as C, createElementVNode as q, renderList as S, createBlock as T } from "vue";
2
+ import { cn as F } from "@phila/phila-ui-core";
3
+ import { Tags as L } from "@phila/phila-ui-tags";
4
+ import './index.css';const N = {
5
+ key: 0,
6
+ class: "phila-filter-summary__results"
7
+ }, B = { class: "phila-filter-summary__query" }, D = {
8
+ key: 1,
9
+ class: "phila-filter-summary__pills"
10
+ }, R = /* @__PURE__ */ A({
11
+ __name: "FilterSummary",
12
+ props: {
13
+ filters: {},
14
+ modelValue: { default: () => ({}) },
15
+ resultsCount: {},
16
+ query: {},
17
+ clearAllText: { default: "Clear all" },
18
+ className: {}
19
+ },
20
+ emits: ["update:modelValue"],
21
+ setup(s, { emit: y }) {
22
+ const a = s, i = y, c = m(() => a.filters.filter((t) => !t.excludeFromCount));
23
+ function k(t, e) {
24
+ return t.choices?.find((l) => l.value === e)?.text ?? e;
25
+ }
26
+ const f = m(() => {
27
+ const t = [];
28
+ for (const e of c.value) {
29
+ const l = a.modelValue[e.key];
30
+ if (Array.isArray(l))
31
+ for (const r of l) {
32
+ const d = k(e, r);
33
+ t.push({ filterKey: e.key, value: r, text: d, ariaLabel: `Remove ${e.label}: ${d}` });
34
+ }
35
+ else if (typeof l == "string" && l) {
36
+ const r = k(e, l);
37
+ t.push({ filterKey: e.key, value: l, text: r, ariaLabel: `Remove ${e.label}: ${r}` });
38
+ } else l === !0 && t.push({ filterKey: e.key, value: !0, text: e.label, ariaLabel: `Remove ${e.label} filter` });
39
+ }
40
+ return t;
41
+ }), p = m(() => f.value.length >= 2), $ = m(() => {
42
+ if (a.resultsCount == null) return "";
43
+ const t = a.resultsCount === 1 ? "result" : "results";
44
+ return `Showing ${a.resultsCount} ${t}`;
45
+ });
46
+ function h(t) {
47
+ const e = {};
48
+ for (const l of Object.keys(t)) {
49
+ const r = t[l];
50
+ e[l] = Array.isArray(r) ? [...r] : r;
51
+ }
52
+ return e;
53
+ }
54
+ function g(t) {
55
+ const e = h(a.modelValue), l = e[t.filterKey];
56
+ Array.isArray(l) ? e[t.filterKey] = l.filter((r) => r !== t.value) : typeof l == "string" ? e[t.filterKey] = "" : l === !0 && (e[t.filterKey] = !1), i("update:modelValue", e);
57
+ }
58
+ function V() {
59
+ const t = h(a.modelValue);
60
+ for (const e of c.value)
61
+ delete t[e.key];
62
+ i("update:modelValue", t);
63
+ }
64
+ return (t, e) => t.$slots.results || s.resultsCount != null || f.value.length ? (o(), u("div", {
65
+ key: 0,
66
+ class: K(b(F)("phila-filter-summary", a.className))
67
+ }, [
68
+ t.$slots.results || s.resultsCount != null ? (o(), u("p", N, [
69
+ _(t.$slots, "results", {
70
+ count: s.resultsCount,
71
+ query: s.query
72
+ }, () => [
73
+ x(v($.value), 1),
74
+ s.query ? (o(), u(C, { key: 0 }, [
75
+ e[0] || (e[0] = x(" for ", -1)),
76
+ q("span", B, '"' + v(s.query) + '"', 1)
77
+ ], 64)) : n("", !0)
78
+ ], !0)
79
+ ])) : n("", !0),
80
+ f.value.length ? (o(), u("div", D, [
81
+ (o(!0), u(C, null, S(f.value, (l, r) => (o(), T(b(L), {
82
+ key: `${l.filterKey}:${String(l.value)}:${r}`,
83
+ variant: "dismissible",
84
+ color: "grey",
85
+ text: l.text,
86
+ "aria-label": l.ariaLabel,
87
+ onDismiss: (d) => g(l)
88
+ }, null, 8, ["text", "aria-label", "onDismiss"]))), 128)),
89
+ p.value ? (o(), u("button", {
90
+ key: 0,
91
+ type: "button",
92
+ class: "phila-filter-summary__clear-all",
93
+ "aria-label": "Clear all filters",
94
+ onClick: V
95
+ }, v(s.clearAllText), 1)) : n("", !0)
96
+ ])) : n("", !0)
97
+ ], 2)) : n("", !0);
98
+ }
99
+ }), w = (s, y) => {
100
+ const a = s.__vccOpts || s;
101
+ for (const [i, c] of y)
102
+ a[i] = c;
103
+ return a;
104
+ }, z = /* @__PURE__ */ w(R, [["__scopeId", "data-v-bcac6c42"]]);
105
+ export {
106
+ z as FilterSummary
107
+ };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@phila/phila-ui-filter-summary",
3
+ "version": "0.1.0-beta.0",
4
+ "type": "module",
5
+ "description": "Filter summary for applied filters",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "keywords": [
20
+ "ui",
21
+ "filter-summary",
22
+ "vue",
23
+ "component"
24
+ ],
25
+ "author": "",
26
+ "license": "MIT",
27
+ "peerDependencies": {
28
+ "vue": "^3.0.0"
29
+ },
30
+ "dependencies": {
31
+ "@phila/phila-ui-core": "3.0.0-beta.5",
32
+ "@phila/phila-ui-tags": "0.1.0-beta.6"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^24.0.0",
36
+ "@vitejs/plugin-vue": "^6.0.1",
37
+ "eslint": "^9.0.0",
38
+ "typescript": "^5.8.3",
39
+ "vite": "^7.0.6",
40
+ "vite-plugin-dts": "^4.5.4",
41
+ "vite-plugin-lib-inject-css": "^2.2.2",
42
+ "@phila/phila-ui-filter-chip": "0.2.0-beta.3",
43
+ "@phila/phila-ui-filter-panel": "0.1.0-beta.0"
44
+ },
45
+ "scripts": {
46
+ "build": "vite build",
47
+ "build-win": "vite build",
48
+ "dev": "vite build --watch",
49
+ "lint": "eslint src --ext .ts,.tsx,.vue",
50
+ "lint:fix": "eslint src --ext .ts,.tsx,.vue --fix",
51
+ "type-check": "tsc --noEmit",
52
+ "clean": "rm -rf dist",
53
+ "format": "prettier --write .",
54
+ "format:check": "prettier --check ."
55
+ }
56
+ }