ember-primitives 0.62.0 → 0.63.1
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 +1 -1
- package/declarations/components/accordion/content.d.ts +2 -1
- package/declarations/components/accordion/content.d.ts.map +1 -1
- package/declarations/components/accordion/header.d.ts +2 -1
- package/declarations/components/accordion/header.d.ts.map +1 -1
- package/declarations/components/accordion/item.d.ts +7 -0
- package/declarations/components/accordion/item.d.ts.map +1 -1
- package/declarations/components/accordion/public.d.ts +3 -3
- package/declarations/components/accordion/public.d.ts.map +1 -1
- package/declarations/components/accordion/trigger.d.ts +2 -1
- package/declarations/components/accordion/trigger.d.ts.map +1 -1
- package/declarations/components/command-palette.d.ts +16 -3
- package/declarations/components/command-palette.d.ts.map +1 -1
- package/declarations/components/dialog.d.ts +14 -6
- package/declarations/components/dialog.d.ts.map +1 -1
- package/declarations/components/drawer.d.ts +14 -6
- package/declarations/components/drawer.d.ts.map +1 -1
- package/declarations/components/link.d.ts.map +1 -1
- package/declarations/components/menu.d.ts.map +1 -1
- package/declarations/components/one-time-password/otp.d.ts +1 -1
- package/declarations/components/one-time-password/utils.d.ts.map +1 -1
- package/declarations/components/rating/range.d.ts +1 -0
- package/declarations/components/rating/range.d.ts.map +1 -1
- package/declarations/components/rating/rating.d.ts +1 -1
- package/declarations/components/rating/rating.d.ts.map +1 -1
- package/declarations/components/rating/stars.d.ts.map +1 -1
- package/declarations/components/rating/state.d.ts +1 -0
- package/declarations/components/rating/state.d.ts.map +1 -1
- package/declarations/components/slider.d.ts +2 -0
- package/declarations/components/slider.d.ts.map +1 -1
- package/declarations/components/switch.d.ts +5 -1
- package/declarations/components/switch.d.ts.map +1 -1
- package/declarations/components/tabs.d.ts +30 -1
- package/declarations/components/tabs.d.ts.map +1 -1
- package/declarations/components/zoetrope/index.d.ts.map +1 -1
- package/dist/components/accordion.js +12 -4
- package/dist/components/accordion.js.map +1 -1
- package/dist/components/command-palette.js +9 -2
- package/dist/components/command-palette.js.map +1 -1
- package/dist/components/dialog.js +9 -2
- package/dist/components/dialog.js.map +1 -1
- package/dist/components/drawer.js +9 -2
- package/dist/components/drawer.js.map +1 -1
- package/dist/components/link.js +1 -1
- package/dist/components/link.js.map +1 -1
- package/dist/components/menu.js +5 -1
- package/dist/components/menu.js.map +1 -1
- package/dist/components/one-time-password.js +1 -1
- package/dist/components/rating.js +1 -1
- package/dist/components/slider.js +6 -1
- package/dist/components/switch.js +1 -1
- package/dist/components/switch.js.map +1 -1
- package/dist/components/tabs.js +18 -5
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/zoetrope/styles.css +6 -1
- package/dist/components/zoetrope.js +1 -1
- package/dist/{index-gRO4Cvlf.js → index-B-yrfUe9.js} +10 -3
- package/dist/index-B-yrfUe9.js.map +1 -0
- package/dist/index.js +3 -3
- package/dist/{otp-H5a4B4yu.js → otp-C8c1EIw6.js} +27 -15
- package/dist/otp-C8c1EIw6.js.map +1 -0
- package/dist/{rating-BrIiwDLw.js → rating-CKWhrr-L.js} +9 -5
- package/dist/rating-CKWhrr-L.js.map +1 -0
- package/package.json +2 -2
- package/src/components/accordion/content.gts +4 -2
- package/src/components/accordion/header.gts +8 -2
- package/src/components/accordion/item.gts +18 -2
- package/src/components/accordion/public.ts +12 -3
- package/src/components/accordion/trigger.gts +5 -3
- package/src/components/command-palette.gts +28 -6
- package/src/components/dialog.gts +15 -3
- package/src/components/drawer.gts +15 -3
- package/src/components/link.gts +1 -0
- package/src/components/menu.gts +7 -0
- package/src/components/one-time-password/input.gts +1 -1
- package/src/components/one-time-password/otp.gts +1 -1
- package/src/components/one-time-password/utils.ts +26 -13
- package/src/components/rating/range.gts +3 -0
- package/src/components/rating/rating.gts +7 -2
- package/src/components/rating/stars.gts +0 -1
- package/src/components/rating/state.gts +9 -0
- package/src/components/slider.gts +8 -0
- package/src/components/switch.gts +5 -2
- package/src/components/tabs.gts +35 -4
- package/src/components/zoetrope/index.gts +12 -3
- package/src/components/zoetrope/styles.css +6 -1
- package/dist/index-gRO4Cvlf.js.map +0 -1
- package/dist/otp-H5a4B4yu.js.map +0 -1
- package/dist/rating-BrIiwDLw.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion.js","sources":["../../src/components/accordion/content.gts","../../src/components/accordion/trigger.gts","../../src/components/accordion/header.gts","../../src/components/accordion/item.gts","../../src/components/accordion.gts"],"sourcesContent":["import Component from \"@glimmer/component\";\n\nimport { getDataState } from \"./item.gts\";\n\nimport type { AccordionContentExternalSignature } from \"./public.ts\";\n\ninterface Signature extends AccordionContentExternalSignature {\n Args: {\n isExpanded: boolean;\n value: string;\n disabled?: boolean;\n };\n}\n\nexport class AccordionContent extends Component<Signature> {\n <template>\n <div\n role=\"region\"\n id={{@value}}\n data-state={{getDataState @isExpanded}}\n hidden={{this.isHidden}}\n data-disabled={{@disabled}}\n ...attributes\n >\n {{yield}}\n </div>\n </template>\n\n get isHidden() {\n return !this.args.isExpanded;\n }\n}\n\nexport default AccordionContent;\n","import { on } from \"@ember/modifier\";\n\nimport { getDataState } from \"./item.gts\";\n\nimport type { AccordionTriggerExternalSignature } from \"./public.ts\";\nimport type { TOC } from \"@ember/component/template-only\";\n\ninterface Signature extends AccordionTriggerExternalSignature {\n Args: {\n isExpanded: boolean;\n value: string;\n disabled?: boolean;\n toggleItem: () => void;\n };\n}\n\nexport const AccordionTrigger: TOC<Signature> = <template>\n <button\n type=\"button\"\n aria-controls={{@value}}\n aria-expanded={{@isExpanded}}\n data-state={{getDataState @isExpanded}}\n data-disabled={{@disabled}}\n aria-disabled={{if @disabled \"true\" \"false\"}}\n {{on \"click\" @toggleItem}}\n ...attributes\n >\n {{yield}}\n </button>\n</template>;\n\nexport default AccordionTrigger;\n","import { hash } from \"@ember/helper\";\n\nimport { getDataState } from \"./item.gts\";\nimport Trigger from \"./trigger.gts\";\n\nimport type { AccordionHeaderExternalSignature } from \"./public.ts\";\nimport type { TOC } from \"@ember/component/template-only\";\n\ninterface Signature extends AccordionHeaderExternalSignature {\n Args: {\n value: string;\n isExpanded: boolean;\n disabled?: boolean;\n toggleItem: () => void;\n };\n}\n\nexport const AccordionHeader: TOC<Signature> = <template>\n <div\n role=\"heading\"\n aria-level=\"3\"\n data-state={{getDataState @isExpanded}}\n data-disabled={{@disabled}}\n ...attributes\n >\n {{yield\n (hash\n Trigger=(component\n Trigger value=@value isExpanded=@isExpanded disabled=@disabled toggleItem=@toggleItem\n )\n )\n }}\n </div>\n</template>;\n\nexport default AccordionHeader;\n","import Component from \"@glimmer/component\";\nimport { hash } from \"@ember/helper\";\n\nimport Content from \"./content.gts\";\nimport Header from \"./header.gts\";\n\nimport type { AccordionItemExternalSignature } from \"./public.ts\";\n\nexport function getDataState(isExpanded: boolean): string {\n return isExpanded ? \"open\" : \"closed\";\n}\n\ninterface Signature extends AccordionItemExternalSignature {\n Args: AccordionItemExternalSignature[\"Args\"] & {\n selectedValue?: string | string[];\n disabled?: boolean;\n toggleItem: (value: string) => void;\n };\n}\n\nexport class AccordionItem extends Component<Signature> {\n <template>\n <div data-state={{getDataState this.isExpanded}} data-disabled={{@disabled}} ...attributes>\n {{yield\n (hash\n isExpanded=this.isExpanded\n Header=(component\n Header\n value=@value\n isExpanded=this.isExpanded\n disabled=@disabled\n toggleItem=this.toggleItem\n )\n Content=(component Content value=@value isExpanded=this.isExpanded disabled=@disabled)\n )\n }}\n </div>\n </template>\n\n get isExpanded(): boolean {\n if (Array.isArray(this.args.selectedValue)) {\n return this.args.selectedValue.includes(this.args.value);\n }\n\n return this.args.selectedValue === this.args.value;\n }\n\n toggleItem = (): void => {\n if (this.args.disabled) return;\n\n this.args.toggleItem(this.args.value);\n };\n}\n\nexport default AccordionItem;\n","import Component from \"@glimmer/component\";\nimport { assert } from \"@ember/debug\";\nimport { hash } from \"@ember/helper\";\n\n// temp\n// https://github.com/tracked-tools/tracked-toolbox/issues/38\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore\nimport { localCopy } from \"tracked-toolbox\";\n\nimport AccordionItem from \"./accordion/item.gts\";\n\nimport type { WithBoundArgs } from \"@glint/template\";\n\ntype AccordionSingleArgs = {\n /**\n * The type of accordion. If `single`, only one item can be selected at a time. If `multiple`, multiple items can be selected at a time.\n */\n type: \"single\";\n /**\n * Whether the accordion is disabled. When `true`, all items cannot be expanded or collapsed.\n */\n disabled?: boolean;\n /**\n * When type is `single`, whether the accordion is collapsible. When `true`, the selected item can be collapsed by clicking its trigger.\n */\n collapsible?: boolean;\n} & (\n | {\n /**\n * The currently selected value. To be used in a controlled fashion in conjunction with `onValueChange`.\n */\n value: string;\n /**\n * A callback that is called when the selected value changes. To be used in a controlled fashion in conjunction with `value`.\n */\n onValueChange: (value: string | undefined) => void;\n /**\n * Not available in a controlled fashion.\n */\n defaultValue?: never;\n }\n | {\n /**\n * Not available in an uncontrolled fashion.\n */\n value?: never;\n /**\n * Not available in an uncontrolled fashion.\n */\n onValueChange?: never;\n /**\n * The default value of the accordion. To be used in an uncontrolled fashion.\n */\n defaultValue?: string;\n }\n);\n\ntype AccordionMultipleArgs = {\n /**\n * The type of accordion. If `single`, only one item can be selected at a time. If `multiple`, multiple items can be selected at a time.\n */\n type: \"multiple\";\n /**\n * Whether the accordion is disabled. When `true`, all items cannot be expanded or collapsed.\n */\n disabled?: boolean;\n} & (\n | {\n /**\n * The currently selected values. To be used in a controlled fashion in conjunction with `onValueChange`.\n */\n value: string[];\n /**\n * A callback that is called when the selected values change. To be used in a controlled fashion in conjunction with `value`.\n */\n onValueChange: (value?: string[]) => void;\n /**\n * Not available in a controlled fashion.\n */\n defaultValue?: never;\n }\n | {\n /**\n * Not available in an uncontrolled fashion.\n */\n value?: never;\n /**\n * Not available in an uncontrolled fashion.\n */\n onValueChange?: never;\n /**\n * The default values of the accordion. To be used in an uncontrolled fashion.\n */\n defaultValue?: string[];\n }\n);\n\nexport class Accordion extends Component<{\n Element: HTMLDivElement;\n Args: AccordionSingleArgs | AccordionMultipleArgs;\n Blocks: {\n default: [\n {\n /**\n * The AccordionItem component.\n */\n Item: WithBoundArgs<typeof AccordionItem, \"selectedValue\" | \"toggleItem\" | \"disabled\">;\n },\n ];\n };\n}> {\n <template>\n <div data-disabled={{@disabled}} ...attributes>\n {{yield\n (hash\n Item=(component\n AccordionItem\n selectedValue=this.selectedValue\n toggleItem=this.toggleItem\n disabled=@disabled\n )\n )\n }}\n </div>\n </template>\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n @localCopy(\"args.defaultValue\") declare _internallyManagedValue?: string | string[];\n\n get selectedValue() {\n return this.args.value ?? this._internallyManagedValue;\n }\n\n toggleItem = (value: string) => {\n if (this.args.disabled) {\n return;\n }\n\n if (this.args.type === \"single\") {\n this.toggleItemSingle(value);\n } else if (this.args.type === \"multiple\") {\n this.toggleItemMultiple(value);\n }\n };\n\n toggleItemSingle = (value: string) => {\n assert(\"Cannot call `toggleItemSingle` when `disabled` is true.\", !this.args.disabled);\n assert(\n \"Cannot call `toggleItemSingle` when `type` is not `single`.\",\n this.args.type === \"single\",\n );\n\n if (value === this.selectedValue && !this.args.collapsible) {\n return;\n }\n\n const newValue = value === this.selectedValue ? undefined : value;\n\n if (this.args.onValueChange) {\n this.args.onValueChange(newValue);\n } else {\n this._internallyManagedValue = newValue;\n }\n };\n\n toggleItemMultiple = (value: string) => {\n assert(\"Cannot call `toggleItemMultiple` when `disabled` is true.\", !this.args.disabled);\n assert(\n \"Cannot call `toggleItemMultiple` when `type` is not `multiple`.\",\n this.args.type === \"multiple\",\n );\n\n const currentValues = (this.selectedValue as string[] | undefined) ?? [];\n const indexOfValue = currentValues.indexOf(value);\n let newValue: string[];\n\n if (indexOfValue === -1) {\n newValue = [...currentValues, value];\n } else {\n newValue = [\n ...currentValues.slice(0, indexOfValue),\n ...currentValues.slice(indexOfValue + 1),\n ];\n }\n\n if (this.args.onValueChange) {\n this.args.onValueChange(newValue);\n } else {\n this._internallyManagedValue = newValue;\n }\n };\n}\n\nexport default Accordion;\n"],"names":["AccordionContent","Component","setComponentTemplate","precompileTemplate","strictMode","scope","getDataState","isHidden","args","isExpanded","AccordionTrigger","on","templateOnly","AccordionHeader","hash","Trigger","AccordionItem","Header","Content","Array","isArray","selectedValue","includes","value","toggleItem","disabled","Accordion","g","prototype","localCopy","i","_internallyManagedValue","type","toggleItemSingle","toggleItemMultiple","assert","collapsible","newValue","undefined","onValueChange","currentValues","indexOfValue","indexOf","slice"],"mappings":";;;;;;;;;;AAcO,MAAMA,yBAAyBC,SAAA,CAAU;AAC9C,EAAA;IAAAC,oBAAA,CAAAC,kBAAA,CAAA,qKAAA,EAWA;MAAAC,UAAA,EAAA,IAAA;AAAAC,MAAAA,KAAA,EAAAA,OAAA;AAAAC,QAAAA;AAAA,OAAA;KAAU,CAAA,EAAV,IAAW,CAAA;AAAD;EAEV,IAAIC,QAAAA,GAAW;AACb,IAAA,OAAO,CAAC,IAAI,CAACC,IAAI,CAACC,UAAU;AAC9B,EAAA;AACF;;ACfO,MAAMC,gBAAsB,GAAAR,oBAAA,CAAaC,kBAAA,CAAA,0QAAA,EAahD;EAAAC,UAAA,EAAA,IAAA;AAAAC,EAAAA,KAAA,EAAAA,OAAA;IAAAC,YAAA;AAAAK,IAAAA;AAAA,GAAA;AAAU,CAAA,CAAA,EAAAC,YAAA,EAAA,CAAA;;ACZH,MAAMC,eAAqB,GAAAX,oBAAA,CAAaC,kBAAA,CAAA,iQAAA,EAgB/C;EAAAC,UAAA,EAAA,IAAA;AAAAC,EAAAA,KAAA,EAAAA,OAAA;IAAAC,YAAA;IAAAQ,IAAA;AAAAC,aAAAA;AAAA,GAAA;AAAU,CAAA,CAAA,EAAAH,YAAA,EAAA,CAAA;;ACzBH,SAASN,YAAAA,CAAaG,UAAmB,EAAS;AACvD,EAAA,OAAOA,aAAa,MAAA,GAAS,QAAA;AAC/B;AAUO,MAAMO,sBAAsBf,SAAA,CAAU;AAC3C,EAAA;IAAAC,oBAAA,CAAAC,kBAAA,CAAA,2VAAA,EAgBA;MAAAC,UAAA,EAAA,IAAA;AAAAC,MAAAA,KAAA,EAAAA,OAAA;QAAAC,YAAA;QAAAQ,IAAA;gBAAAG,eAAA;AAAAC,iBAAAA;AAAA,OAAA;KAAU,CAAA,EAAV,IAAW,CAAA;AAAD;EAEV,IAAIT,UAAAA,GAAsB;IACxB,IAAIU,KAAA,CAAMC,OAAO,CAAC,IAAI,CAACZ,IAAI,CAACa,aAAa,CAAA,EAAG;AAC1C,MAAA,OAAO,IAAI,CAACb,IAAI,CAACa,aAAa,CAACC,QAAQ,CAAC,IAAI,CAACd,IAAI,CAACe,KAAK,CAAA;AACzD,IAAA;IAEA,OAAO,IAAI,CAACf,IAAI,CAACa,aAAa,KAAK,IAAI,CAACb,IAAI,CAACe,KAAK;AACpD,EAAA;EAEAC,UAAA,GAAaA,MAAQ;AACnB,IAAA,IAAI,IAAI,CAAChB,IAAI,CAACiB,QAAQ,EAAE;IAExB,IAAI,CAACjB,IAAI,CAACgB,UAAU,CAAC,IAAI,CAAChB,IAAI,CAACe,KAAK,CAAA;EACtC,CAAA;AACF;;AC8CO,MAAMG,SAAA,SAAkBzB,SAAA;AAc7B,EAAA;IAAAC,oBAAA,CAAAC,kBAAA,CAAA,2LAAA,EAaA;MAAAC,UAAA,EAAA,IAAA;AAAAC,MAAAA,KAAA,EAAAA,OAAA;QAAAS,IAAA;AAAAE,QAAAA;AAAA,OAAA;KAAU,CAAA,EAAV,IAAW,CAAA;AAAD;AAEV;AAAA,EAAA;AAAAW,IAAAA,CAAA,MAAAC,SAAA,EAAA,yBAAA,EAAA,CACCC,SAAA,CAAU,mBAAA,CAAA,CAAA,CAAA;AAAA;EAAA,wBAAA,IAAAC,CAAA,CAAA,IAAA,EAAA,yBAAA,CAAA,EAAA,MAAA;EAEX,IAAIT,aAAAA,GAAgB;IAClB,OAAO,IAAI,CAACb,IAAI,CAACe,KAAK,IAAI,IAAI,CAACQ,uBAAuB;AACxD,EAAA;EAEAP,UAAA,GAAcD,KAAa,IAAA;AACzB,IAAA,IAAI,IAAI,CAACf,IAAI,CAACiB,QAAQ,EAAE;AACtB,MAAA;AACF,IAAA;AAEA,IAAA,IAAI,IAAI,CAACjB,IAAI,CAACwB,IAAI,KAAK,QAAA,EAAU;AAC/B,MAAA,IAAI,CAACC,gBAAgB,CAACV,KAAA,CAAA;IACxB,CAAA,MAAO,IAAI,IAAI,CAACf,IAAI,CAACwB,IAAI,KAAK,UAAA,EAAY;AACxC,MAAA,IAAI,CAACE,kBAAkB,CAACX,KAAA,CAAA;AAC1B,IAAA;EACF,CAAA;EAEAU,gBAAA,GAAoBV,KAAa,IAAA;IAC/BY,MAAA,CAAO,2DAA2D,CAAC,IAAI,CAAC3B,IAAI,CAACiB,QAAQ,CAAA;IACrFU,MAAA,CACE,+DACA,IAAI,CAAC3B,IAAI,CAACwB,IAAI,KAAK,QAAA,CAAA;AAGrB,IAAA,IAAIT,KAAA,KAAU,IAAI,CAACF,aAAa,IAAI,CAAC,IAAI,CAACb,IAAI,CAAC4B,WAAW,EAAE;AAC1D,MAAA;AACF,IAAA;IAEA,MAAMC,WAAWd,KAAA,KAAU,IAAI,CAACF,aAAa,GAAGiB,SAAA,GAAYf,KAAA;AAE5D,IAAA,IAAI,IAAI,CAACf,IAAI,CAAC+B,aAAa,EAAE;AAC3B,MAAA,IAAI,CAAC/B,IAAI,CAAC+B,aAAa,CAACF,QAAA,CAAA;AAC1B,IAAA,CAAA,MAAO;MACL,IAAI,CAACN,uBAAuB,GAAGM,QAAA;AACjC,IAAA;EACF,CAAA;EAEAH,kBAAA,GAAsBX,KAAa,IAAA;IACjCY,MAAA,CAAO,6DAA6D,CAAC,IAAI,CAAC3B,IAAI,CAACiB,QAAQ,CAAA;IACvFU,MAAA,CACE,mEACA,IAAI,CAAC3B,IAAI,CAACwB,IAAI,KAAK,UAAA,CAAA;AAGrB,IAAA,MAAMQ,aAAA,GAAiB,IAAI,CAACnB,aAAa,IAA6B,EAAE;AACxE,IAAA,MAAMoB,YAAA,GAAeD,aAAA,CAAcE,OAAO,CAACnB,KAAA,CAAA;AAC3C,IAAA,IAAIc,QAAgB;AAEpB,IAAA,IAAII,YAAA,KAAiB,EAAC,EAAG;AACvBJ,MAAAA,QAAA,GAAW,CAAI,GAAAG,aAAA,EAAejB,KAAA,CAAM;AACtC,IAAA,CAAA,MAAO;MACLc,QAAA,GAAW,IACNG,aAAA,CAAcG,KAAK,CAAC,CAAA,EAAGF,YAAA,CAAA,KACvBD,aAAA,CAAcG,KAAK,CAACF,YAAA,GAAe,CAAA,CAAA,CACvC;AACH,IAAA;AAEA,IAAA,IAAI,IAAI,CAACjC,IAAI,CAAC+B,aAAa,EAAE;AAC3B,MAAA,IAAI,CAAC/B,IAAI,CAAC+B,aAAa,CAACF,QAAA,CAAA;AAC1B,IAAA,CAAA,MAAO;MACL,IAAI,CAACN,uBAAuB,GAAGM,QAAA;AACjC,IAAA;EACF,CAAA;AACF;;;;"}
|
|
1
|
+
{"version":3,"file":"accordion.js","sources":["../../src/components/accordion/content.gts","../../src/components/accordion/trigger.gts","../../src/components/accordion/header.gts","../../src/components/accordion/item.gts","../../src/components/accordion.gts"],"sourcesContent":["import Component from \"@glimmer/component\";\n\nimport { getDataState } from \"./item.gts\";\n\nimport type { AccordionContentExternalSignature } from \"./public.ts\";\n\ninterface Signature extends AccordionContentExternalSignature {\n Args: {\n isExpanded: boolean;\n triggerId: string;\n contentId: string;\n disabled?: boolean;\n };\n}\n\nexport class AccordionContent extends Component<Signature> {\n <template>\n <div\n role=\"region\"\n id={{@contentId}}\n aria-labelledby={{@triggerId}}\n data-state={{getDataState @isExpanded}}\n hidden={{this.isHidden}}\n data-disabled={{@disabled}}\n ...attributes\n >\n {{yield}}\n </div>\n </template>\n\n get isHidden() {\n return !this.args.isExpanded;\n }\n}\n\nexport default AccordionContent;\n","import { on } from \"@ember/modifier\";\n\nimport { getDataState } from \"./item.gts\";\n\nimport type { AccordionTriggerExternalSignature } from \"./public.ts\";\nimport type { TOC } from \"@ember/component/template-only\";\n\ninterface Signature extends AccordionTriggerExternalSignature {\n Args: {\n isExpanded: boolean;\n triggerId: string;\n contentId: string;\n disabled?: boolean;\n toggleItem: () => void;\n };\n}\n\nexport const AccordionTrigger: TOC<Signature> = <template>\n <button\n type=\"button\"\n id={{@triggerId}}\n aria-controls={{@contentId}}\n aria-expanded={{if @isExpanded \"true\" \"false\"}}\n data-state={{getDataState @isExpanded}}\n data-disabled={{@disabled}}\n aria-disabled={{if @disabled \"true\" \"false\"}}\n {{on \"click\" @toggleItem}}\n ...attributes\n >\n {{yield}}\n </button>\n</template>;\n\nexport default AccordionTrigger;\n","import { hash } from \"@ember/helper\";\n\nimport { getDataState } from \"./item.gts\";\nimport Trigger from \"./trigger.gts\";\n\nimport type { AccordionHeaderExternalSignature } from \"./public.ts\";\nimport type { TOC } from \"@ember/component/template-only\";\n\ninterface Signature extends AccordionHeaderExternalSignature {\n Args: {\n triggerId: string;\n contentId: string;\n isExpanded: boolean;\n disabled?: boolean;\n toggleItem: () => void;\n };\n}\n\nexport const AccordionHeader: TOC<Signature> = <template>\n <div\n role=\"heading\"\n aria-level=\"3\"\n data-state={{getDataState @isExpanded}}\n data-disabled={{@disabled}}\n ...attributes\n >\n {{yield\n (hash\n Trigger=(component\n Trigger\n triggerId=@triggerId\n contentId=@contentId\n isExpanded=@isExpanded\n disabled=@disabled\n toggleItem=@toggleItem\n )\n )\n }}\n </div>\n</template>;\n\nexport default AccordionHeader;\n","import Component from \"@glimmer/component\";\nimport { hash } from \"@ember/helper\";\n\nimport { uniqueId } from \"../../utils.ts\";\nimport Content from \"./content.gts\";\nimport Header from \"./header.gts\";\n\nimport type { AccordionItemExternalSignature } from \"./public.ts\";\n\nexport function getDataState(isExpanded: boolean): string {\n return isExpanded ? \"open\" : \"closed\";\n}\n\ninterface Signature extends AccordionItemExternalSignature {\n Args: AccordionItemExternalSignature[\"Args\"] & {\n selectedValue?: string | string[];\n disabled?: boolean;\n toggleItem: (value: string) => void;\n };\n}\n\nexport class AccordionItem extends Component<Signature> {\n /**\n * `@value` only has to be unique within one accordion.\n * Element ids have to be unique across the document.\n */\n id = `ember-primitives__accordion-${uniqueId()}`;\n triggerId = `${this.id}__trigger`;\n contentId = `${this.id}__content`;\n\n <template>\n <div data-state={{getDataState this.isExpanded}} data-disabled={{@disabled}} ...attributes>\n {{yield\n (hash\n isExpanded=this.isExpanded\n Header=(component\n Header\n triggerId=this.triggerId\n contentId=this.contentId\n isExpanded=this.isExpanded\n disabled=@disabled\n toggleItem=this.toggleItem\n )\n Content=(component\n Content\n triggerId=this.triggerId\n contentId=this.contentId\n isExpanded=this.isExpanded\n disabled=@disabled\n )\n )\n }}\n </div>\n </template>\n\n get isExpanded(): boolean {\n if (Array.isArray(this.args.selectedValue)) {\n return this.args.selectedValue.includes(this.args.value);\n }\n\n return this.args.selectedValue === this.args.value;\n }\n\n toggleItem = (): void => {\n if (this.args.disabled) return;\n\n this.args.toggleItem(this.args.value);\n };\n}\n\nexport default AccordionItem;\n","import Component from \"@glimmer/component\";\nimport { assert } from \"@ember/debug\";\nimport { hash } from \"@ember/helper\";\n\n// temp\n// https://github.com/tracked-tools/tracked-toolbox/issues/38\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore\nimport { localCopy } from \"tracked-toolbox\";\n\nimport AccordionItem from \"./accordion/item.gts\";\n\nimport type { WithBoundArgs } from \"@glint/template\";\n\ntype AccordionSingleArgs = {\n /**\n * The type of accordion. If `single`, only one item can be selected at a time. If `multiple`, multiple items can be selected at a time.\n */\n type: \"single\";\n /**\n * Whether the accordion is disabled. When `true`, all items cannot be expanded or collapsed.\n */\n disabled?: boolean;\n /**\n * When type is `single`, whether the accordion is collapsible. When `true`, the selected item can be collapsed by clicking its trigger.\n */\n collapsible?: boolean;\n} & (\n | {\n /**\n * The currently selected value. To be used in a controlled fashion in conjunction with `onValueChange`.\n */\n value: string;\n /**\n * A callback that is called when the selected value changes. To be used in a controlled fashion in conjunction with `value`.\n */\n onValueChange: (value: string | undefined) => void;\n /**\n * Not available in a controlled fashion.\n */\n defaultValue?: never;\n }\n | {\n /**\n * Not available in an uncontrolled fashion.\n */\n value?: never;\n /**\n * Not available in an uncontrolled fashion.\n */\n onValueChange?: never;\n /**\n * The default value of the accordion. To be used in an uncontrolled fashion.\n */\n defaultValue?: string;\n }\n);\n\ntype AccordionMultipleArgs = {\n /**\n * The type of accordion. If `single`, only one item can be selected at a time. If `multiple`, multiple items can be selected at a time.\n */\n type: \"multiple\";\n /**\n * Whether the accordion is disabled. When `true`, all items cannot be expanded or collapsed.\n */\n disabled?: boolean;\n} & (\n | {\n /**\n * The currently selected values. To be used in a controlled fashion in conjunction with `onValueChange`.\n */\n value: string[];\n /**\n * A callback that is called when the selected values change. To be used in a controlled fashion in conjunction with `value`.\n */\n onValueChange: (value?: string[]) => void;\n /**\n * Not available in a controlled fashion.\n */\n defaultValue?: never;\n }\n | {\n /**\n * Not available in an uncontrolled fashion.\n */\n value?: never;\n /**\n * Not available in an uncontrolled fashion.\n */\n onValueChange?: never;\n /**\n * The default values of the accordion. To be used in an uncontrolled fashion.\n */\n defaultValue?: string[];\n }\n);\n\nexport class Accordion extends Component<{\n Element: HTMLDivElement;\n Args: AccordionSingleArgs | AccordionMultipleArgs;\n Blocks: {\n default: [\n {\n /**\n * The AccordionItem component.\n */\n Item: WithBoundArgs<typeof AccordionItem, \"selectedValue\" | \"toggleItem\" | \"disabled\">;\n },\n ];\n };\n}> {\n <template>\n <div data-disabled={{@disabled}} ...attributes>\n {{yield\n (hash\n Item=(component\n AccordionItem\n selectedValue=this.selectedValue\n toggleItem=this.toggleItem\n disabled=@disabled\n )\n )\n }}\n </div>\n </template>\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n @localCopy(\"args.defaultValue\") declare _internallyManagedValue?: string | string[];\n\n get selectedValue() {\n return this.args.value ?? this._internallyManagedValue;\n }\n\n toggleItem = (value: string) => {\n if (this.args.disabled) {\n return;\n }\n\n if (this.args.type === \"single\") {\n this.toggleItemSingle(value);\n } else if (this.args.type === \"multiple\") {\n this.toggleItemMultiple(value);\n }\n };\n\n toggleItemSingle = (value: string) => {\n assert(\"Cannot call `toggleItemSingle` when `disabled` is true.\", !this.args.disabled);\n assert(\n \"Cannot call `toggleItemSingle` when `type` is not `single`.\",\n this.args.type === \"single\",\n );\n\n if (value === this.selectedValue && !this.args.collapsible) {\n return;\n }\n\n const newValue = value === this.selectedValue ? undefined : value;\n\n if (this.args.onValueChange) {\n this.args.onValueChange(newValue);\n } else {\n this._internallyManagedValue = newValue;\n }\n };\n\n toggleItemMultiple = (value: string) => {\n assert(\"Cannot call `toggleItemMultiple` when `disabled` is true.\", !this.args.disabled);\n assert(\n \"Cannot call `toggleItemMultiple` when `type` is not `multiple`.\",\n this.args.type === \"multiple\",\n );\n\n const currentValues = (this.selectedValue as string[] | undefined) ?? [];\n const indexOfValue = currentValues.indexOf(value);\n let newValue: string[];\n\n if (indexOfValue === -1) {\n newValue = [...currentValues, value];\n } else {\n newValue = [\n ...currentValues.slice(0, indexOfValue),\n ...currentValues.slice(indexOfValue + 1),\n ];\n }\n\n if (this.args.onValueChange) {\n this.args.onValueChange(newValue);\n } else {\n this._internallyManagedValue = newValue;\n }\n };\n}\n\nexport default Accordion;\n"],"names":["AccordionContent","Component","setComponentTemplate","precompileTemplate","strictMode","scope","getDataState","isHidden","args","isExpanded","AccordionTrigger","on","templateOnly","AccordionHeader","hash","Trigger","AccordionItem","id","uniqueId","triggerId","contentId","Header","Content","Array","isArray","selectedValue","includes","value","toggleItem","disabled","Accordion","g","prototype","localCopy","i","_internallyManagedValue","type","toggleItemSingle","toggleItemMultiple","assert","collapsible","newValue","undefined","onValueChange","currentValues","indexOfValue","indexOf","slice"],"mappings":"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"}
|
|
@@ -9,6 +9,7 @@ import { modifier } from 'ember-modifier';
|
|
|
9
9
|
import { getTabster } from 'tabster';
|
|
10
10
|
import { localCopy } from 'tracked-toolbox';
|
|
11
11
|
import { Link } from './link.js';
|
|
12
|
+
import { VisuallyHidden } from './visually-hidden.js';
|
|
12
13
|
import { precompileTemplate } from '@ember/template-compilation';
|
|
13
14
|
import { setComponentTemplate } from '@ember/component';
|
|
14
15
|
import templateOnly from '@ember/component/template-only';
|
|
@@ -86,7 +87,7 @@ class LinkItem extends Component {
|
|
|
86
87
|
* rows and tell a screen reader about neither, so the pointer sets the same
|
|
87
88
|
* state the arrow keys do instead of painting its own.
|
|
88
89
|
*/
|
|
89
|
-
const List = setComponentTemplate(precompileTemplate("<div id={{@id}} role=\"listbox\" {{@register}} {{on \"click\" @onClick}} {{on \"pointermove\" @onPointerMove}} ...attributes>\n {{yield (hash Item=@
|
|
90
|
+
const List = setComponentTemplate(precompileTemplate("<div id={{@id}} role=\"listbox\" {{@register}} {{on \"click\" @onClick}} {{on \"pointermove\" @onPointerMove}} ...attributes>\n {{yield (hash Item=@item LinkItem=@linkItem)}}\n</div>", {
|
|
90
91
|
strictMode: true,
|
|
91
92
|
scope: () => ({
|
|
92
93
|
on,
|
|
@@ -154,6 +155,11 @@ class CommandPalette extends Component {
|
|
|
154
155
|
get placeholder() {
|
|
155
156
|
return this.args.placeholder ?? "Search";
|
|
156
157
|
}
|
|
158
|
+
get resultsText() {
|
|
159
|
+
const count = this.args.items?.length ?? 0;
|
|
160
|
+
const custom = this.args.resultsText?.(count);
|
|
161
|
+
return custom ?? (count === 1 ? "1 result" : `${count} results`);
|
|
162
|
+
}
|
|
157
163
|
/**
|
|
158
164
|
* Whether `@items` was passed, not whether it has anything in it: an empty
|
|
159
165
|
* array is falsy in a template, and a palette whose results have gone away
|
|
@@ -343,7 +349,7 @@ class CommandPalette extends Component {
|
|
|
343
349
|
this.#select(option, event);
|
|
344
350
|
};
|
|
345
351
|
static {
|
|
346
|
-
setComponentTemplate(precompileTemplate("{{#let (component Input listId=this.listId activeId=this.activeId query=this.query onInput=this.handleInput onKeydown=this.handleKeydown) (component List id=this.listId register=this.registerList onPointerMove=this.handlePointerMove onClick=this.handleClick
|
|
352
|
+
setComponentTemplate(precompileTemplate("{{#let (component Input listId=this.listId activeId=this.activeId query=this.query onInput=this.handleInput onKeydown=this.handleKeydown) (component List id=this.listId register=this.registerList onPointerMove=this.handlePointerMove onClick=this.handleClick item=(component Item activeId=this.activeId) linkItem=(component LinkItem activeId=this.activeId)) as |PaletteInput PaletteList|}}\n {{#if this.hasItems}}\n {{#if (has-block)}}{{this.bothGiven}}{{/if}}\n\n <PaletteInput class=\"ember-primitives__command-palette__input\" placeholder={{this.placeholder}} aria-label={{this.placeholder}} />\n\n <PaletteList class=\"ember-primitives__command-palette__list\" as |l|>\n {{#each @items as |item|}}\n <l.Item class=\"ember-primitives__command-palette__item\">\n {{#if (iconOf item)}}\n <span class=\"ember-primitives__command-palette__icon\">{{iconOf item}}</span>\n {{/if}}\n <span class=\"ember-primitives__command-palette__label\">{{labelOf item}}</span>\n {{#if (descriptionOf item)}}\n <span class=\"ember-primitives__command-palette__description\">{{descriptionOf item}}</span>\n {{/if}}\n </l.Item>\n {{/each}}\n </PaletteList>\n\n <VisuallyHidden role=\"status\">{{this.resultsText}}</VisuallyHidden>\n {{else}}\n {{yield (hash query=this.query setQuery=this.setQuery Input=PaletteInput List=PaletteList)}}\n {{/if}}\n{{/let}}", {
|
|
347
353
|
strictMode: true,
|
|
348
354
|
scope: () => ({
|
|
349
355
|
Input,
|
|
@@ -353,6 +359,7 @@ class CommandPalette extends Component {
|
|
|
353
359
|
iconOf,
|
|
354
360
|
labelOf,
|
|
355
361
|
descriptionOf,
|
|
362
|
+
VisuallyHidden,
|
|
356
363
|
hash
|
|
357
364
|
})
|
|
358
365
|
}), this);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-palette.js","sources":["../../src/components/command-palette.gts"],"sourcesContent":["/**\n * References:\n * - https://www.w3.org/WAI/ARIA/apg/patterns/combobox/\n * - https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-activedescendant\n *\n * A combobox (the input) over a listbox (the results).\n *\n * Focus never leaves the input; `aria-activedescendant` is what moves. This is\n * why there is no tabster mover here: the arrow keys must not move focus, or\n * the user stops being able to type. Tabster still does the finding.\n *\n * Filtering is not this component's job. Render the results you want, in the\n * order you want.\n */\nimport Component from \"@glimmer/component\";\nimport { tracked } from \"@glimmer/tracking\";\nimport { assert } from \"@ember/debug\";\nimport { registerDestructor } from \"@ember/destroyable\";\nimport { hash } from \"@ember/helper\";\nimport { on } from \"@ember/modifier\";\nimport { guidFor } from \"@ember/object/internals\";\n\nimport { modifier as eModifier } from \"ember-modifier\";\nimport { getTabster } from \"tabster\";\n// temp\n// https://github.com/tracked-tools/tracked-toolbox/issues/38\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-expect-error\nimport { localCopy } from \"tracked-toolbox\";\n\nimport { Link, type Signature as LinkSignature } from \"./link.gts\";\n\nimport type { TOC } from \"@ember/component/template-only\";\nimport type Owner from \"@ember/owner\";\nimport type { ModifierLike, WithBoundArgs } from \"@glint/template\";\n\nconst OPTION = '[role=\"option\"]';\n\nfunction isMac() {\n return navigator.userAgent.includes(\"Mac OS\");\n}\n\n/**\n * Matches a hotkey description, such as `\"mod+k\"`, against a keyboard event.\n *\n * `mod` is <kbd>Meta</kbd> on macOS and <kbd>Control</kbd> everywhere else,\n * the same normalization `<KeyCombo>` uses to render one.\n */\nfunction matches(event: KeyboardEvent, hotkey: string) {\n const parts = hotkey\n .toLowerCase()\n .split(\"+\")\n .map((part) => part.trim());\n const key = parts.pop();\n const modifiers = new Set(parts);\n const mod = modifiers.has(\"mod\");\n\n return (\n event.key.toLowerCase() === key &&\n event.metaKey === (modifiers.has(\"meta\") || (mod && isMac())) &&\n event.ctrlKey === (modifiers.has(\"ctrl\") || (mod && !isMac())) &&\n event.altKey === modifiers.has(\"alt\") &&\n event.shiftKey === modifiers.has(\"shift\")\n );\n}\n\nexport interface ItemSignature {\n Element: HTMLDivElement;\n Blocks: { default: [] };\n}\n\ninterface PrivateItemSignature {\n Element: ItemSignature[\"Element\"];\n Args: { activeId: string | undefined };\n Blocks: ItemSignature[\"Blocks\"];\n}\n\nclass Item extends Component<PrivateItemSignature> {\n id = guidFor(this);\n\n get isActive() {\n return this.args.activeId === this.id;\n }\n\n <template>\n <div\n id={{this.id}}\n role=\"option\"\n tabindex=\"-1\"\n aria-selected=\"{{this.isActive}}\"\n data-active=\"{{this.isActive}}\"\n ...attributes\n >\n {{yield}}\n </div>\n </template>\n}\n\nexport interface LinkItemSignature {\n Element: HTMLAnchorElement;\n Args: LinkSignature[\"Args\"];\n Blocks: { default: [] };\n}\n\ninterface PrivateLinkItemSignature {\n Element: LinkItemSignature[\"Element\"];\n Args: LinkItemSignature[\"Args\"] & { activeId: string | undefined };\n Blocks: LinkItemSignature[\"Blocks\"];\n}\n\n/**\n * An option that is also a link. <kbd>Enter</kbd> dispatches a real click on\n * the anchor, so the router navigates exactly as it would have for a mouse.\n */\nclass LinkItem extends Component<PrivateLinkItemSignature> {\n id = guidFor(this);\n\n get isActive() {\n return this.args.activeId === this.id;\n }\n\n <template>\n <Link\n id={{this.id}}\n role=\"option\"\n tabindex=\"-1\"\n aria-selected=\"{{this.isActive}}\"\n data-active=\"{{this.isActive}}\"\n @href={{@href}}\n @includeActiveQueryParams={{@includeActiveQueryParams}}\n @activeOnSubPaths={{@activeOnSubPaths}}\n ...attributes\n >\n {{yield}}\n </Link>\n </template>\n}\n\nexport interface ListSignature {\n Element: HTMLDivElement;\n Blocks: {\n default: [\n {\n Item: WithBoundArgs<typeof Item, \"activeId\">;\n LinkItem: WithBoundArgs<typeof LinkItem, \"activeId\">;\n },\n ];\n };\n}\n\ninterface PrivateListSignature {\n Element: ListSignature[\"Element\"];\n Args: {\n id: string;\n register: ModifierLike<{ Element: HTMLElement }>;\n onPointerMove: (event: PointerEvent) => void;\n onClick: (event: MouseEvent) => void;\n Item: ListSignature[\"Blocks\"][\"default\"][0][\"Item\"];\n LinkItem: ListSignature[\"Blocks\"][\"default\"][0][\"LinkItem\"];\n };\n Blocks: ListSignature[\"Blocks\"];\n}\n\n/**\n * The pointer is handled here rather than on each option, and on\n * `pointermove` rather than `pointerenter`, so that an option under a resting\n * cursor re-activates when the cursor moves after the keyboard has activated\n * something else. Native menus behave this way.\n *\n * `:hover` cannot do this job. There is one active option, it is what\n * <kbd>Enter</kbd> chooses, and it is what `aria-activedescendant` reports.\n * Hovering while the keyboard has a different option active would light two\n * rows and tell a screen reader about neither, so the pointer sets the same\n * state the arrow keys do instead of painting its own.\n */\nconst List: TOC<PrivateListSignature> = <template>\n <div\n id={{@id}}\n role=\"listbox\"\n {{@register}}\n {{on \"click\" @onClick}}\n {{on \"pointermove\" @onPointerMove}}\n ...attributes\n >\n {{yield (hash Item=@Item LinkItem=@LinkItem)}}\n </div>\n</template>;\n\nexport interface InputSignature {\n Element: HTMLInputElement;\n}\n\ninterface PrivateInputSignature {\n Element: InputSignature[\"Element\"];\n Args: {\n listId: string;\n activeId: string | undefined;\n query: string;\n onInput: (event: Event) => void;\n onKeydown: (event: KeyboardEvent) => void;\n };\n}\n\nconst Input: TOC<PrivateInputSignature> = <template>\n <input\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n aria-controls={{@listId}}\n aria-activedescendant={{@activeId}}\n value={{@query}}\n {{on \"input\" @onInput}}\n {{on \"keydown\" @onKeydown}}\n ...attributes\n />\n</template>;\n\n/**\n * One entry in the default layout. A bare string is the label.\n */\nexport type PaletteItem =\n | string\n | {\n label: string;\n description?: string;\n icon?: string;\n };\n\nconst labelOf = (item: PaletteItem) => (typeof item === \"string\" ? item : item.label);\nconst descriptionOf = (item: PaletteItem) =>\n typeof item === \"string\" ? undefined : item.description;\nconst iconOf = (item: PaletteItem) => (typeof item === \"string\" ? undefined : item.icon);\n\n/**\n * The default layout: hand it the rows and it renders the whole palette.\n */\nexport interface ItemsSignature {\n Args: {\n /**\n * The text in the input.\n *\n * The state is managed internally, so this does not need to be a\n * maintained value, but whenever it changes, the input reflects it. Pair\n * it with `@onQueryChange` to keep the query somewhere else, such as a\n * query param.\n */\n query?: string;\n /**\n * Called with the input's text every time it changes.\n */\n onQueryChange?: (query: string) => void;\n /**\n * A key combination that calls `@onOpen` from anywhere on the page, such\n * as `\"mod+k\"`. `mod` is <kbd>Meta</kbd> on macOS and <kbd>Control</kbd>\n * everywhere else.\n *\n * Needs `@onOpen` to have anything to do. No listener is installed\n * without both.\n */\n hotkey?: string;\n /**\n * Called when `@hotkey` is pressed. Hand it the `open` of whatever the\n * palette is in:\n *\n * ```hbs\n * <CommandPalette @hotkey=\"mod+k\" @onOpen={{d.open}} />\n * ```\n */\n onOpen?: () => void;\n /**\n * The entries to render. Each is a string, or an object with a `label`\n * and optionally a `description` and an `icon`.\n */\n items: PaletteItem[];\n /**\n * Called every time a row is chosen, with the entry that was chosen.\n * This is where a modal palette closes itself:\n *\n * ```hbs\n * <Dialog as |d|>\n * <CommandPalette @items={{this.commands}} @onSelect={{d.close}} />\n * </Dialog>\n * ```\n *\n * Also where a row's own action goes, since the entry it is handed says\n * which row was chosen.\n */\n onSelect?: (item: PaletteItem, event: Event) => void;\n /**\n * The input's placeholder, and its accessible name.\n *\n * Defaults to \"Search\".\n */\n placeholder?: string;\n };\n /**\n * No blocks: this form renders the rows. Passing one is an error.\n */\n Blocks: Record<string, never>;\n}\n\n/**\n * The composed form: you render the rows.\n */\nexport interface BlockSignature {\n Args: {\n /**\n * The text in the input.\n *\n * The state is managed internally, so this does not need to be a\n * maintained value, but whenever it changes, the input reflects it. Pair\n * it with `@onQueryChange` to keep the query somewhere else, such as a\n * query param.\n */\n query?: string;\n /**\n * Called with the input's text every time it changes.\n */\n onQueryChange?: (query: string) => void;\n /**\n * A key combination that calls `@onOpen` from anywhere on the page, such\n * as `\"mod+k\"`. `mod` is <kbd>Meta</kbd> on macOS and <kbd>Control</kbd>\n * everywhere else.\n *\n * Needs `@onOpen` to have anything to do. No listener is installed\n * without both.\n */\n hotkey?: string;\n /**\n * Called when `@hotkey` is pressed. Hand it the `open` of whatever the\n * palette is in:\n *\n * ```hbs\n * <CommandPalette @hotkey=\"mod+k\" @onOpen={{d.open}} />\n * ```\n */\n onOpen?: () => void;\n /**\n * Not for this form: the rows come from the block.\n */\n items?: never;\n /**\n * Not for this form: set it on `Input` yourself.\n */\n placeholder?: never;\n /**\n * Called every time a row is chosen. Hand it `close` to make a modal\n * palette close itself.\n *\n * The rows are yours here, so there is no entry to hand back. Which row\n * was chosen is `event.target.closest(\"[role=option]\")`.\n */\n onSelect?: (event: Event) => void;\n };\n Blocks: {\n default: [\n {\n /**\n * The current text of the input.\n */\n query: string;\n /**\n * Sets the text of the input, for a \"clear\" button or a suggestion.\n */\n setQuery: (query: string) => void;\n /**\n * The `<input>`, wired as a combobox over `List`.\n */\n Input: WithBoundArgs<\n typeof Input,\n \"listId\" | \"activeId\" | \"query\" | \"onInput\" | \"onKeydown\"\n >;\n /**\n * The listbox the rows are rendered into.\n */\n List: WithBoundArgs<\n typeof List,\n \"id\" | \"register\" | \"onPointerMove\" | \"onClick\" | \"Item\" | \"LinkItem\"\n >;\n },\n ];\n };\n}\n\nexport type Signature = ItemsSignature | BlockSignature;\n\nexport class CommandPalette extends Component<Signature> {\n listId = guidFor(this);\n\n /**\n * Held rather than looked up by id, because `document.getElementById` does\n * not cross into a shadow root. A plain field: it is read when a key is\n * pressed, never while rendering.\n */\n #list: HTMLElement | undefined;\n\n /**\n * Which row is active, as an id rather than an element or a focus state.\n *\n * This is unusual for this library, where keyboard navigation means tabster\n * moving focus. A combobox cannot do that: focus has to stay in the\n * `<input>` or the reader stops being able to type. So nothing among the\n * rows is ever focused, there is no focus for tabster to track, and what\n * moves instead is `aria-activedescendant` -- which is an id, on the input,\n * pointing at a row. Holding the id is holding exactly what that attribute\n * needs.\n *\n * Tabster still does the finding, in `#find`. This only remembers which of\n * the rows it landed on.\n */\n @tracked activeId: string | undefined;\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n @localCopy(\"args.query\") declare _query: string;\n\n constructor(owner: Owner, args: Signature[\"Args\"]) {\n super(owner, args);\n\n document.addEventListener(\"keydown\", this.handleHotkey);\n\n registerDestructor(this, () => {\n document.removeEventListener(\"keydown\", this.handleHotkey);\n });\n }\n\n get placeholder() {\n return this.args.placeholder ?? \"Search\";\n }\n\n /**\n * Whether `@items` was passed, not whether it has anything in it: an empty\n * array is falsy in a template, and a palette whose results have gone away\n * still has to render the input they would be typed into.\n */\n get hasItems() {\n return this.args.items !== undefined;\n }\n\n /**\n * Each form is called the way its own type describes: with the entry that\n * was chosen, or with the event alone when the rows are the caller's.\n *\n * `@items` is what tells the two apart, and it is `never` on the form that\n * does not take it, so testing it narrows `this.args` to one of them.\n */\n #select(option: HTMLElement, event: Event) {\n if (this.args.items === undefined) {\n this.args.onSelect?.(event);\n\n return;\n }\n\n const entry = this.#entryFor(option, this.args.items);\n\n assert(\n \"[BUG] a row of the default layout was chosen, but is not among `@items`\",\n entry !== undefined,\n );\n\n this.args.onSelect?.(entry, event);\n }\n\n /**\n * `@items` and a block are two ways to say the same thing, and saying both\n * means one of them is being quietly ignored.\n */\n get bothGiven() {\n assert(\n \"<CommandPalette> was given both `@items` and a block. Use one: `@items` renders the rows for you, a block renders them yourself.\",\n false,\n );\n\n return \"\";\n }\n\n get query() {\n return this._query ?? \"\";\n }\n set query(value: string) {\n this._query = value;\n }\n\n registerList = eModifier((element: HTMLElement) => {\n this.#list = element;\n });\n\n get #activeElement() {\n const { activeId } = this;\n\n if (!activeId) return undefined;\n\n // scoped to the listbox, so this works inside a shadow root\n return this.#list?.querySelector<HTMLElement>(`[id=\"${activeId}\"]`) ?? undefined;\n }\n\n /**\n * The next, previous, first or last option.\n *\n * Tabster does the finding, so hidden and inert options are skipped by the\n * same rules as everything else that moves around the page. It has to be\n * set up by the app, the same way `<Menu>` requires it.\n */\n #find(direction: \"next\" | \"prev\" | \"first\" | \"last\") {\n const container = this.#list;\n\n if (!container) return undefined;\n\n const tabster = getTabster(window);\n\n assert(\n \"<CommandPalette> needs tabster, which the application sets up. \" +\n \"Call `setupTabster` from 'ember-primitives/tabster' in your application route. \" +\n \"See https://tabster.io/docs/core\",\n tabster,\n );\n\n const options = { container, includeProgrammaticallyFocusable: true };\n const currentElement = this.#activeElement;\n const { focusable } = tabster;\n\n if (direction === \"first\") return focusable.findFirst(options);\n if (direction === \"last\") return focusable.findLast(options);\n\n if (!currentElement) {\n return direction === \"next\" ? focusable.findFirst(options) : focusable.findLast(options);\n }\n\n const found =\n direction === \"next\"\n ? focusable.findNext({ ...options, currentElement })\n : focusable.findPrev({ ...options, currentElement });\n\n // wrap, rather than stop, at either end\n return (\n found ?? (direction === \"next\" ? focusable.findFirst(options) : focusable.findLast(options))\n );\n }\n\n #activate(element: HTMLElement | null | undefined) {\n if (!element) return;\n\n this.activeId = element.id;\n element.scrollIntoView({ block: \"nearest\" });\n }\n\n setQuery = (query: string) => {\n this.query = query;\n // the results are about to be somebody else's; whatever was active is not\n this.activeId = undefined;\n this.args.onQueryChange?.(query);\n };\n\n handleInput = (event: Event) => {\n const { target } = event;\n\n assert(\"[BUG] input event without an input\", target instanceof HTMLInputElement);\n\n this.setQuery(target.value);\n };\n\n handlePointerMove = (event: PointerEvent) => {\n const { target } = event;\n\n if (!(target instanceof Element)) return;\n\n const option = target.closest<HTMLElement>(OPTION);\n\n if (option) {\n this.activeId = option.id;\n }\n };\n\n handleKeydown = (event: KeyboardEvent) => {\n // mid-composition (IME), the arrow keys belong to the candidate window\n if (event.isComposing) return;\n\n switch (event.key) {\n case \"ArrowDown\": {\n event.preventDefault();\n this.#activate(this.#find(\"next\"));\n\n return;\n }\n case \"ArrowUp\": {\n event.preventDefault();\n this.#activate(this.#find(\"prev\"));\n\n return;\n }\n case \"Enter\": {\n // nothing arrowed yet chooses the first result, so a reader can type\n // and press Enter without leaving the keys they were already on\n const active = this.#activeElement ?? this.#find(\"first\");\n\n if (!active) return;\n\n event.preventDefault();\n // a real click, so one handler covers the mouse and the keyboard, and\n // an anchor navigates the way the browser would have\n active.click();\n\n return;\n }\n /**\n * Home and End are left to the browser: in an editable combobox they\n * move the caret, which is what the APG asks for.\n */\n }\n };\n\n handleHotkey = (event: KeyboardEvent) => {\n const { hotkey, onOpen } = this.args;\n\n if (!hotkey || !onOpen) return;\n if (!matches(event, hotkey)) return;\n\n event.preventDefault();\n onOpen();\n };\n\n /**\n * The entry a row came from, for the default layout. Rows are rendered one\n * per entry and in order, so a row's position among the options is its\n * entry's position in `@items`. A block form has no entries, and gets\n * `undefined`.\n */\n #entryFor(option: HTMLElement, items: PaletteItem[]) {\n const options = this.#list?.querySelectorAll<HTMLElement>(OPTION);\n\n for (let i = 0; i < (options?.length ?? 0); i++) {\n if (options?.[i] === option) return items[i];\n }\n\n return undefined;\n }\n\n /**\n * Choosing is delegated, so a row is markup rather than a listener, and\n * `@onSelect` is reached the same way however the rows were rendered.\n */\n handleClick = (event: MouseEvent) => {\n const { target } = event;\n\n if (!(target instanceof Element)) return;\n\n const option = target.closest<HTMLElement>(OPTION);\n\n if (!option) return;\n\n /**\n * A modified click on a link opens it somewhere else and leaves the\n * reader where they are, so the palette stays where they left it.\n */\n if (option.closest(\"a\")) {\n if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;\n if (event.button !== 0) return;\n }\n\n this.#select(option, event);\n };\n\n <template>\n {{#let\n (component\n Input\n listId=this.listId\n activeId=this.activeId\n query=this.query\n onInput=this.handleInput\n onKeydown=this.handleKeydown\n )\n (component\n List\n id=this.listId\n register=this.registerList\n onPointerMove=this.handlePointerMove\n onClick=this.handleClick\n Item=(component Item activeId=this.activeId)\n LinkItem=(component LinkItem activeId=this.activeId)\n )\n as |PaletteInput PaletteList|\n }}\n {{#if this.hasItems}}\n {{#if (has-block)}}{{this.bothGiven}}{{/if}}\n\n <PaletteInput\n class=\"ember-primitives__command-palette__input\"\n placeholder={{this.placeholder}}\n aria-label={{this.placeholder}}\n />\n\n <PaletteList class=\"ember-primitives__command-palette__list\" as |l|>\n {{#each @items as |item|}}\n <l.Item class=\"ember-primitives__command-palette__item\">\n {{#if (iconOf item)}}\n <span class=\"ember-primitives__command-palette__icon\">{{iconOf item}}</span>\n {{/if}}\n <span class=\"ember-primitives__command-palette__label\">{{labelOf item}}</span>\n {{#if (descriptionOf item)}}\n <span class=\"ember-primitives__command-palette__description\">{{descriptionOf\n item\n }}</span>\n {{/if}}\n </l.Item>\n {{/each}}\n </PaletteList>\n {{else}}\n {{yield (hash query=this.query setQuery=this.setQuery Input=PaletteInput List=PaletteList)}}\n {{/if}}\n {{/let}}\n </template>\n}\n\nexport default CommandPalette;\n"],"names":["OPTION","isMac","navigator","userAgent","includes","matches","event","hotkey","parts","toLowerCase","split","map","part","trim","key","pop","modifiers","Set","mod","has","metaKey","ctrlKey","altKey","shiftKey","Item","Component","id","guidFor","isActive","args","activeId","setComponentTemplate","precompileTemplate","strictMode","LinkItem","scope","Link","List","on","hash","templateOnly","Input","labelOf","item","label","descriptionOf","undefined","description","iconOf","icon","CommandPalette","listId","g","prototype","tracked","i","localCopy","constructor","owner","document","addEventListener","handleHotkey","registerDestructor","removeEventListener","placeholder","hasItems","items","#select","option","onSelect","entry","assert","bothGiven","query","_query","value","registerList","eModifier","element","#activeElement","querySelector","#find","direction","container","tabster","getTabster","window","options","includeProgrammaticallyFocusable","currentElement","focusable","findFirst","findLast","found","findNext","findPrev","#activate","scrollIntoView","block","setQuery","onQueryChange","handleInput","target","HTMLInputElement","handlePointerMove","Element","closest","handleKeydown","isComposing","preventDefault","active","click","onOpen","#entryFor","querySelectorAll","length","handleClick","button"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"command-palette.js","sources":["../../src/components/command-palette.gts"],"sourcesContent":["/**\n * References:\n * - https://www.w3.org/WAI/ARIA/apg/patterns/combobox/\n * - https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-activedescendant\n *\n * A combobox (the input) over a listbox (the results).\n *\n * Focus never leaves the input; `aria-activedescendant` is what moves. This is\n * why there is no tabster mover here: the arrow keys must not move focus, or\n * the user stops being able to type. Tabster still does the finding.\n *\n * Filtering is not this component's job. Render the results you want, in the\n * order you want.\n */\nimport Component from \"@glimmer/component\";\nimport { tracked } from \"@glimmer/tracking\";\nimport { assert } from \"@ember/debug\";\nimport { registerDestructor } from \"@ember/destroyable\";\nimport { hash } from \"@ember/helper\";\nimport { on } from \"@ember/modifier\";\nimport { guidFor } from \"@ember/object/internals\";\n\nimport { modifier as eModifier } from \"ember-modifier\";\nimport { getTabster } from \"tabster\";\n// temp\n// https://github.com/tracked-tools/tracked-toolbox/issues/38\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-expect-error\nimport { localCopy } from \"tracked-toolbox\";\n\nimport { Link, type Signature as LinkSignature } from \"./link.gts\";\nimport { VisuallyHidden } from \"./visually-hidden.gts\";\n\nimport type { TOC } from \"@ember/component/template-only\";\nimport type Owner from \"@ember/owner\";\nimport type { ModifierLike, WithBoundArgs } from \"@glint/template\";\n\nconst OPTION = '[role=\"option\"]';\n\nfunction isMac() {\n return navigator.userAgent.includes(\"Mac OS\");\n}\n\n/**\n * Matches a hotkey description, such as `\"mod+k\"`, against a keyboard event.\n *\n * `mod` is <kbd>Meta</kbd> on macOS and <kbd>Control</kbd> everywhere else,\n * the same normalization `<KeyCombo>` uses to render one.\n */\nfunction matches(event: KeyboardEvent, hotkey: string) {\n const parts = hotkey\n .toLowerCase()\n .split(\"+\")\n .map((part) => part.trim());\n const key = parts.pop();\n const modifiers = new Set(parts);\n const mod = modifiers.has(\"mod\");\n\n return (\n event.key.toLowerCase() === key &&\n event.metaKey === (modifiers.has(\"meta\") || (mod && isMac())) &&\n event.ctrlKey === (modifiers.has(\"ctrl\") || (mod && !isMac())) &&\n event.altKey === modifiers.has(\"alt\") &&\n event.shiftKey === modifiers.has(\"shift\")\n );\n}\n\nexport interface ItemSignature {\n Element: HTMLDivElement;\n Blocks: { default: [] };\n}\n\ninterface PrivateItemSignature {\n Element: ItemSignature[\"Element\"];\n Args: { activeId: string | undefined };\n Blocks: ItemSignature[\"Blocks\"];\n}\n\nclass Item extends Component<PrivateItemSignature> {\n id = guidFor(this);\n\n get isActive() {\n return this.args.activeId === this.id;\n }\n\n <template>\n <div\n id={{this.id}}\n role=\"option\"\n tabindex=\"-1\"\n aria-selected=\"{{this.isActive}}\"\n data-active=\"{{this.isActive}}\"\n ...attributes\n >\n {{yield}}\n </div>\n </template>\n}\n\nexport interface LinkItemSignature {\n Element: HTMLAnchorElement;\n Args: LinkSignature[\"Args\"];\n Blocks: { default: [] };\n}\n\ninterface PrivateLinkItemSignature {\n Element: LinkItemSignature[\"Element\"];\n Args: LinkItemSignature[\"Args\"] & { activeId: string | undefined };\n Blocks: LinkItemSignature[\"Blocks\"];\n}\n\n/**\n * An option that is also a link. <kbd>Enter</kbd> dispatches a real click on\n * the anchor, so the router navigates exactly as it would have for a mouse.\n */\nclass LinkItem extends Component<PrivateLinkItemSignature> {\n id = guidFor(this);\n\n get isActive() {\n return this.args.activeId === this.id;\n }\n\n <template>\n <Link\n id={{this.id}}\n role=\"option\"\n tabindex=\"-1\"\n aria-selected=\"{{this.isActive}}\"\n data-active=\"{{this.isActive}}\"\n @href={{@href}}\n @includeActiveQueryParams={{@includeActiveQueryParams}}\n @activeOnSubPaths={{@activeOnSubPaths}}\n ...attributes\n >\n {{yield}}\n </Link>\n </template>\n}\n\nexport interface ListSignature {\n Element: HTMLDivElement;\n Blocks: {\n default: [\n {\n Item: WithBoundArgs<typeof Item, \"activeId\">;\n LinkItem: WithBoundArgs<typeof LinkItem, \"activeId\">;\n },\n ];\n };\n}\n\ninterface PrivateListSignature {\n Element: ListSignature[\"Element\"];\n Args: {\n id: string;\n register: ModifierLike<{ Element: HTMLElement }>;\n onPointerMove: (event: PointerEvent) => void;\n onClick: (event: MouseEvent) => void;\n item: ListSignature[\"Blocks\"][\"default\"][0][\"Item\"];\n linkItem: ListSignature[\"Blocks\"][\"default\"][0][\"LinkItem\"];\n };\n Blocks: ListSignature[\"Blocks\"];\n}\n\n/**\n * The pointer is handled here rather than on each option, and on\n * `pointermove` rather than `pointerenter`, so that an option under a resting\n * cursor re-activates when the cursor moves after the keyboard has activated\n * something else. Native menus behave this way.\n *\n * `:hover` cannot do this job. There is one active option, it is what\n * <kbd>Enter</kbd> chooses, and it is what `aria-activedescendant` reports.\n * Hovering while the keyboard has a different option active would light two\n * rows and tell a screen reader about neither, so the pointer sets the same\n * state the arrow keys do instead of painting its own.\n */\nconst List: TOC<PrivateListSignature> = <template>\n <div\n id={{@id}}\n role=\"listbox\"\n {{@register}}\n {{on \"click\" @onClick}}\n {{on \"pointermove\" @onPointerMove}}\n ...attributes\n >\n {{yield (hash Item=@item LinkItem=@linkItem)}}\n </div>\n</template>;\n\nexport interface InputSignature {\n Element: HTMLInputElement;\n}\n\ninterface PrivateInputSignature {\n Element: InputSignature[\"Element\"];\n Args: {\n listId: string;\n activeId: string | undefined;\n query: string;\n onInput: (event: Event) => void;\n onKeydown: (event: KeyboardEvent) => void;\n };\n}\n\nconst Input: TOC<PrivateInputSignature> = <template>\n <input\n type=\"text\"\n role=\"combobox\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n aria-controls={{@listId}}\n aria-activedescendant={{@activeId}}\n value={{@query}}\n {{on \"input\" @onInput}}\n {{on \"keydown\" @onKeydown}}\n ...attributes\n />\n</template>;\n\n/**\n * One entry in the default layout. A bare string is the label.\n */\nexport type PaletteItem =\n | string\n | {\n label: string;\n description?: string;\n icon?: string;\n };\n\nconst labelOf = (item: PaletteItem) => (typeof item === \"string\" ? item : item.label);\nconst descriptionOf = (item: PaletteItem) =>\n typeof item === \"string\" ? undefined : item.description;\nconst iconOf = (item: PaletteItem) => (typeof item === \"string\" ? undefined : item.icon);\n\n/**\n * The default layout: hand it the rows and it renders the whole palette.\n */\nexport interface ItemsSignature {\n Args: {\n /**\n * The text in the input.\n *\n * The state is managed internally, so this does not need to be a\n * maintained value, but whenever it changes, the input reflects it. Pair\n * it with `@onQueryChange` to keep the query somewhere else, such as a\n * query param.\n */\n query?: string;\n /**\n * Called with the input's text every time it changes.\n */\n onQueryChange?: (query: string) => void;\n /**\n * A key combination that calls `@onOpen` from anywhere on the page, such\n * as `\"mod+k\"`. `mod` is <kbd>Meta</kbd> on macOS and <kbd>Control</kbd>\n * everywhere else.\n *\n * Needs `@onOpen` to have anything to do. No listener is installed\n * without both.\n */\n hotkey?: string;\n /**\n * Called when `@hotkey` is pressed. Hand it the `open` of whatever the\n * palette is in:\n *\n * ```hbs\n * <CommandPalette @hotkey=\"mod+k\" @onOpen={{d.open}} />\n * ```\n */\n onOpen?: () => void;\n /**\n * The entries to render. Each is a string, or an object with a `label`\n * and optionally a `description` and an `icon`.\n */\n items: PaletteItem[];\n /**\n * Called every time a row is chosen, with the entry that was chosen.\n * This is where a modal palette closes itself:\n *\n * ```hbs\n * <Dialog as |d|>\n * <CommandPalette @items={{this.commands}} @onSelect={{d.close}} />\n * </Dialog>\n * ```\n *\n * Also where a row's own action goes, since the entry it is handed says\n * which row was chosen.\n */\n onSelect?: (item: PaletteItem, event: Event) => void;\n /**\n * The input's placeholder, and its accessible name.\n *\n * Defaults to \"Search\".\n */\n placeholder?: string;\n /**\n * What a screen reader announces when the number of results changes.\n *\n * Defaults to \"1 result\" and \"3 results\".\n */\n resultsText?: (count: number) => string;\n };\n /**\n * No blocks: this form renders the rows. Passing one is an error.\n */\n Blocks: Record<string, never>;\n}\n\n/**\n * The composed form: you render the rows.\n */\nexport interface BlockSignature {\n Args: {\n /**\n * The text in the input.\n *\n * The state is managed internally, so this does not need to be a\n * maintained value, but whenever it changes, the input reflects it. Pair\n * it with `@onQueryChange` to keep the query somewhere else, such as a\n * query param.\n */\n query?: string;\n /**\n * Called with the input's text every time it changes.\n */\n onQueryChange?: (query: string) => void;\n /**\n * A key combination that calls `@onOpen` from anywhere on the page, such\n * as `\"mod+k\"`. `mod` is <kbd>Meta</kbd> on macOS and <kbd>Control</kbd>\n * everywhere else.\n *\n * Needs `@onOpen` to have anything to do. No listener is installed\n * without both.\n */\n hotkey?: string;\n /**\n * Called when `@hotkey` is pressed. Hand it the `open` of whatever the\n * palette is in:\n *\n * ```hbs\n * <CommandPalette @hotkey=\"mod+k\" @onOpen={{d.open}} />\n * ```\n */\n onOpen?: () => void;\n /**\n * Not for this form: the rows come from the block.\n */\n items?: never;\n /**\n * Not for this form: set it on `Input` yourself.\n */\n placeholder?: never;\n /**\n * Not for this form: the rows are yours, and so is announcing them.\n */\n resultsText?: never;\n /**\n * Called every time a row is chosen. Hand it `close` to make a modal\n * palette close itself.\n *\n * The rows are yours here, so there is no entry to hand back. Which row\n * was chosen is `event.target.closest(\"[role=option]\")`.\n */\n onSelect?: (event: Event) => void;\n };\n Blocks: {\n default: [\n {\n /**\n * The current text of the input.\n */\n query: string;\n /**\n * Sets the text of the input, for a \"clear\" button or a suggestion.\n */\n setQuery: (query: string) => void;\n /**\n * The `<input>`, wired as a combobox over `List`.\n *\n * It needs a name: pass `aria-label`, or an `id` and a `<label>`.\n */\n Input: WithBoundArgs<\n typeof Input,\n \"listId\" | \"activeId\" | \"query\" | \"onInput\" | \"onKeydown\"\n >;\n /**\n * The listbox the rows are rendered into.\n */\n List: WithBoundArgs<\n typeof List,\n \"id\" | \"register\" | \"onPointerMove\" | \"onClick\" | \"item\" | \"linkItem\"\n >;\n },\n ];\n };\n}\n\nexport type Signature = ItemsSignature | BlockSignature;\n\nexport class CommandPalette extends Component<Signature> {\n listId = guidFor(this);\n\n /**\n * Held rather than looked up by id, because `document.getElementById` does\n * not cross into a shadow root. A plain field: it is read when a key is\n * pressed, never while rendering.\n */\n #list: HTMLElement | undefined;\n\n /**\n * Which row is active, as an id rather than an element or a focus state.\n *\n * This is unusual for this library, where keyboard navigation means tabster\n * moving focus. A combobox cannot do that: focus has to stay in the\n * `<input>` or the reader stops being able to type. So nothing among the\n * rows is ever focused, there is no focus for tabster to track, and what\n * moves instead is `aria-activedescendant` -- which is an id, on the input,\n * pointing at a row. Holding the id is holding exactly what that attribute\n * needs.\n *\n * Tabster still does the finding, in `#find`. This only remembers which of\n * the rows it landed on.\n */\n @tracked activeId: string | undefined;\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n @localCopy(\"args.query\") declare _query: string;\n\n constructor(owner: Owner, args: Signature[\"Args\"]) {\n super(owner, args);\n\n document.addEventListener(\"keydown\", this.handleHotkey);\n\n registerDestructor(this, () => {\n document.removeEventListener(\"keydown\", this.handleHotkey);\n });\n }\n\n get placeholder() {\n return this.args.placeholder ?? \"Search\";\n }\n\n get resultsText() {\n const count = this.args.items?.length ?? 0;\n const custom = this.args.resultsText?.(count);\n\n return custom ?? (count === 1 ? \"1 result\" : `${count} results`);\n }\n\n /**\n * Whether `@items` was passed, not whether it has anything in it: an empty\n * array is falsy in a template, and a palette whose results have gone away\n * still has to render the input they would be typed into.\n */\n get hasItems() {\n return this.args.items !== undefined;\n }\n\n /**\n * Each form is called the way its own type describes: with the entry that\n * was chosen, or with the event alone when the rows are the caller's.\n *\n * `@items` is what tells the two apart, and it is `never` on the form that\n * does not take it, so testing it narrows `this.args` to one of them.\n */\n #select(option: HTMLElement, event: Event) {\n if (this.args.items === undefined) {\n this.args.onSelect?.(event);\n\n return;\n }\n\n const entry = this.#entryFor(option, this.args.items);\n\n assert(\n \"[BUG] a row of the default layout was chosen, but is not among `@items`\",\n entry !== undefined,\n );\n\n this.args.onSelect?.(entry, event);\n }\n\n /**\n * `@items` and a block are two ways to say the same thing, and saying both\n * means one of them is being quietly ignored.\n */\n get bothGiven() {\n assert(\n \"<CommandPalette> was given both `@items` and a block. Use one: `@items` renders the rows for you, a block renders them yourself.\",\n false,\n );\n\n return \"\";\n }\n\n get query() {\n return this._query ?? \"\";\n }\n set query(value: string) {\n this._query = value;\n }\n\n registerList = eModifier((element: HTMLElement) => {\n this.#list = element;\n });\n\n get #activeElement() {\n const { activeId } = this;\n\n if (!activeId) return undefined;\n\n // scoped to the listbox, so this works inside a shadow root\n return this.#list?.querySelector<HTMLElement>(`[id=\"${activeId}\"]`) ?? undefined;\n }\n\n /**\n * The next, previous, first or last option.\n *\n * Tabster does the finding, so hidden and inert options are skipped by the\n * same rules as everything else that moves around the page. It has to be\n * set up by the app, the same way `<Menu>` requires it.\n */\n #find(direction: \"next\" | \"prev\" | \"first\" | \"last\") {\n const container = this.#list;\n\n if (!container) return undefined;\n\n const tabster = getTabster(window);\n\n assert(\n \"<CommandPalette> needs tabster, which the application sets up. \" +\n \"Call `setupTabster` from 'ember-primitives/tabster' in your application route. \" +\n \"See https://tabster.io/docs/core\",\n tabster,\n );\n\n const options = { container, includeProgrammaticallyFocusable: true };\n const currentElement = this.#activeElement;\n const { focusable } = tabster;\n\n if (direction === \"first\") return focusable.findFirst(options);\n if (direction === \"last\") return focusable.findLast(options);\n\n if (!currentElement) {\n return direction === \"next\" ? focusable.findFirst(options) : focusable.findLast(options);\n }\n\n const found =\n direction === \"next\"\n ? focusable.findNext({ ...options, currentElement })\n : focusable.findPrev({ ...options, currentElement });\n\n // wrap, rather than stop, at either end\n return (\n found ?? (direction === \"next\" ? focusable.findFirst(options) : focusable.findLast(options))\n );\n }\n\n #activate(element: HTMLElement | null | undefined) {\n if (!element) return;\n\n this.activeId = element.id;\n element.scrollIntoView({ block: \"nearest\" });\n }\n\n setQuery = (query: string) => {\n this.query = query;\n // the results are about to be somebody else's; whatever was active is not\n this.activeId = undefined;\n this.args.onQueryChange?.(query);\n };\n\n handleInput = (event: Event) => {\n const { target } = event;\n\n assert(\"[BUG] input event without an input\", target instanceof HTMLInputElement);\n\n this.setQuery(target.value);\n };\n\n handlePointerMove = (event: PointerEvent) => {\n const { target } = event;\n\n if (!(target instanceof Element)) return;\n\n const option = target.closest<HTMLElement>(OPTION);\n\n if (option) {\n this.activeId = option.id;\n }\n };\n\n handleKeydown = (event: KeyboardEvent) => {\n // mid-composition (IME), the arrow keys belong to the candidate window\n if (event.isComposing) return;\n\n switch (event.key) {\n case \"ArrowDown\": {\n event.preventDefault();\n this.#activate(this.#find(\"next\"));\n\n return;\n }\n case \"ArrowUp\": {\n event.preventDefault();\n this.#activate(this.#find(\"prev\"));\n\n return;\n }\n case \"Enter\": {\n // nothing arrowed yet chooses the first result, so a reader can type\n // and press Enter without leaving the keys they were already on\n const active = this.#activeElement ?? this.#find(\"first\");\n\n if (!active) return;\n\n event.preventDefault();\n // a real click, so one handler covers the mouse and the keyboard, and\n // an anchor navigates the way the browser would have\n active.click();\n\n return;\n }\n /**\n * Home and End are left to the browser: in an editable combobox they\n * move the caret, which is what the APG asks for.\n */\n }\n };\n\n handleHotkey = (event: KeyboardEvent) => {\n const { hotkey, onOpen } = this.args;\n\n if (!hotkey || !onOpen) return;\n if (!matches(event, hotkey)) return;\n\n event.preventDefault();\n onOpen();\n };\n\n /**\n * The entry a row came from, for the default layout. Rows are rendered one\n * per entry and in order, so a row's position among the options is its\n * entry's position in `@items`. A block form has no entries, and gets\n * `undefined`.\n */\n #entryFor(option: HTMLElement, items: PaletteItem[]) {\n const options = this.#list?.querySelectorAll<HTMLElement>(OPTION);\n\n for (let i = 0; i < (options?.length ?? 0); i++) {\n if (options?.[i] === option) return items[i];\n }\n\n return undefined;\n }\n\n /**\n * Choosing is delegated, so a row is markup rather than a listener, and\n * `@onSelect` is reached the same way however the rows were rendered.\n */\n handleClick = (event: MouseEvent) => {\n const { target } = event;\n\n if (!(target instanceof Element)) return;\n\n const option = target.closest<HTMLElement>(OPTION);\n\n if (!option) return;\n\n /**\n * A modified click on a link opens it somewhere else and leaves the\n * reader where they are, so the palette stays where they left it.\n */\n if (option.closest(\"a\")) {\n if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;\n if (event.button !== 0) return;\n }\n\n this.#select(option, event);\n };\n\n <template>\n {{#let\n (component\n Input\n listId=this.listId\n activeId=this.activeId\n query=this.query\n onInput=this.handleInput\n onKeydown=this.handleKeydown\n )\n (component\n List\n id=this.listId\n register=this.registerList\n onPointerMove=this.handlePointerMove\n onClick=this.handleClick\n item=(component Item activeId=this.activeId)\n linkItem=(component LinkItem activeId=this.activeId)\n )\n as |PaletteInput PaletteList|\n }}\n {{#if this.hasItems}}\n {{#if (has-block)}}{{this.bothGiven}}{{/if}}\n\n <PaletteInput\n class=\"ember-primitives__command-palette__input\"\n placeholder={{this.placeholder}}\n aria-label={{this.placeholder}}\n />\n\n <PaletteList class=\"ember-primitives__command-palette__list\" as |l|>\n {{#each @items as |item|}}\n <l.Item class=\"ember-primitives__command-palette__item\">\n {{#if (iconOf item)}}\n <span class=\"ember-primitives__command-palette__icon\">{{iconOf item}}</span>\n {{/if}}\n <span class=\"ember-primitives__command-palette__label\">{{labelOf item}}</span>\n {{#if (descriptionOf item)}}\n <span class=\"ember-primitives__command-palette__description\">{{descriptionOf\n item\n }}</span>\n {{/if}}\n </l.Item>\n {{/each}}\n </PaletteList>\n\n <VisuallyHidden role=\"status\">{{this.resultsText}}</VisuallyHidden>\n {{else}}\n {{yield (hash query=this.query setQuery=this.setQuery Input=PaletteInput List=PaletteList)}}\n {{/if}}\n {{/let}}\n </template>\n}\n\nexport default CommandPalette;\n"],"names":["OPTION","isMac","navigator","userAgent","includes","matches","event","hotkey","parts","toLowerCase","split","map","part","trim","key","pop","modifiers","Set","mod","has","metaKey","ctrlKey","altKey","shiftKey","Item","Component","id","guidFor","isActive","args","activeId","setComponentTemplate","precompileTemplate","strictMode","LinkItem","scope","Link","List","on","hash","templateOnly","Input","labelOf","item","label","descriptionOf","undefined","description","iconOf","icon","CommandPalette","listId","g","prototype","tracked","i","localCopy","constructor","owner","document","addEventListener","handleHotkey","registerDestructor","removeEventListener","placeholder","resultsText","count","items","length","custom","hasItems","#select","option","onSelect","entry","assert","bothGiven","query","_query","value","registerList","eModifier","element","#activeElement","querySelector","#find","direction","container","tabster","getTabster","window","options","includeProgrammaticallyFocusable","currentElement","focusable","findFirst","findLast","found","findNext","findPrev","#activate","scrollIntoView","block","setQuery","onQueryChange","handleInput","target","HTMLInputElement","handlePointerMove","Element","closest","handleKeydown","isComposing","preventDefault","active","click","onOpen","#entryFor","querySelectorAll","handleClick","button","VisuallyHidden"],"mappings":";;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;;AAaC;AAwBD,MAAMA,MAAA,GAAS,iBAAA;AAEf,SAASC,KAAAA,GAAA;AACP,EAAA,OAAOC,SAAA,CAAUC,SAAS,CAACC,QAAQ,CAAC,QAAA,CAAA;AACtC;AAEA;;;;;AAKC;AACD,SAASC,OAAAA,CAAQC,KAAoB,EAAEC,MAAc,EAAA;EACnD,MAAMC,KAAA,GAAQD,MAAA,CACXE,WAAW,EAAA,CACXC,KAAK,CAAC,GAAA,CAAA,CACNC,GAAG,CAAEC,IAAA,IAASA,IAAA,CAAKC,IAAI,EAAA,CAAA;AAC1B,EAAA,MAAMC,GAAA,GAAMN,MAAMO,GAAG,EAAA;AACrB,EAAA,MAAMC,SAAA,GAAY,IAAIC,GAAA,CAAIT,KAAA,CAAA;AAC1B,EAAA,MAAMU,GAAA,GAAMF,SAAA,CAAUG,GAAG,CAAC,KAAA,CAAA;AAE1B,EAAA,OACEb,MAAMQ,GAAG,CAACL,WAAW,EAAA,KAAOK,GAAA,IAC5BR,MAAMc,OAAO,MAAMJ,SAAA,CAAUG,GAAG,CAAC,MAAA,CAAA,IAAYD,OAAOjB,KAAA,EAAQ,CAAA,IAC5DK,KAAA,CAAMe,OAAO,MAAML,SAAA,CAAUG,GAAG,CAAC,MAAA,CAAA,IAAYD,OAAO,CAACjB,KAAA,EAAQ,CAAA,IAC7DK,KAAA,CAAMgB,MAAM,KAAKN,SAAA,CAAUG,GAAG,CAAC,KAAA,CAAA,IAC/Bb,KAAA,CAAMiB,QAAQ,KAAKP,SAAA,CAAUG,GAAG,CAAC,OAAA,CACnC;AACF;AAaA,MAAMK,aAAaC,SAAA,CAAU;AAC3BC,EAAAA,EAAA,GAAKC,OAAA,CAAQ,IAAI,CAAA;EAEjB,IAAIC,QAAAA,GAAW;IACb,OAAO,IAAI,CAACC,IAAI,CAACC,QAAQ,KAAK,IAAI,CAACJ,EAAE;AACvC,EAAA;AAEA,EAAA;IAAAK,oBAAA,CAAAC,kBAAA,CAAA,+JAAA,EAWA;MAAAC,UAAA,EAAA;KAAU,CAAA,EAAV,IAAW,CAAA;AAAD;AACZ;AAcA;;;;AAIA,MAAMC,iBAAiBT,SAAA,CAAU;AAC/BC,EAAAA,EAAA,GAAKC,OAAA,CAAQ,IAAI,CAAA;EAEjB,IAAIC,QAAAA,GAAW;IACb,OAAO,IAAI,CAACC,IAAI,CAACC,QAAQ,KAAK,IAAI,CAACJ,EAAE;AACvC,EAAA;AAEA,EAAA;IAAAK,oBAAA,CAAAC,kBAAA,CAAA,iRAAA,EAcA;MAAAC,UAAA,EAAA,IAAA;AAAAE,MAAAA,KAAA,EAAAA,OAAA;AAAAC,QAAAA;AAAA,OAAA;KAAU,CAAA,EAAV,IAAW,CAAA;AAAD;AACZ;AA2BA;;;;;;;;;;;AAWC;AACD,MAAMC,IAAU,GAAAN,oBAAA,CAAwBC,kBAAA,CAAA,yLAAA,EAWxC;EAAAC,UAAA,EAAA,IAAA;AAAAE,EAAAA,KAAA,EAAAA,OAAA;IAAAG,EAAA;AAAAC,IAAAA;AAAA,GAAA;AAAU,CAAA,CAAA,EAAAC,YAAA,EAAA,CAAA;AAiBV,MAAMC,KAAW,GAAAV,oBAAA,CAAyBC,kBAAA,CAAA,uUAAA,EAiB1C;EAAAC,UAAA,EAAA,IAAA;AAAAE,EAAAA,KAAA,EAAAA,OAAA;AAAAG,IAAAA;AAAA,GAAA;AAAU,CAAA,CAAA,EAAAE,YAAA,EAAA,CAAA;AAEV;;;;AAWA,MAAME,OAAA,GAAWC,IAAM,IAAiB,OAAOA,IAAA,KAAS,QAAA,GAAWA,IAAA,GAAOA,IAAA,CAAKC,KAAK;AACpF,MAAMC,aAAA,GAAiBF,IAAM,IAC3B,OAAOA,IAAA,KAAS,QAAA,GAAWG,SAAA,GAAYH,IAAA,CAAKI,WAAW;AACzD,MAAMC,MAAA,GAAUL,IAAM,IAAiB,OAAOA,IAAA,KAAS,QAAA,GAAWG,SAAA,GAAYH,IAAA,CAAKM,IAAI;AAEvF;;AAEC;;AAwED;;AAEC;;AAyFM,MAAMC,uBAAuBzB,SAAA,CAAU;AAC5C0B,EAAAA,MAAA,GAASxB,OAAA,CAAQ,IAAI,CAAA;AAErB;;;;AAIC;AACD,EAAA,KAAK;AAEL;;;;;;;;;;;;;AAaC;AAbD,EAAA;IAAAyB,CAAA,CAAA,IAAA,CAAAC,SAAA,EAAA,UAAA,EAAA,CAcCC,OAAA,CAAA,CAAA;AAAA;EAAA,SAAA,IAAAC,CAAA,CAAA,IAAA,EAAA,UAAA,CAAA,EAAA,MAAA;AAAA,EAAA;AAAAH,IAAAA,CAAA,MAAAC,SAAA,EAAA,QAAA,EAAA,CAGAG,SAAA,CAAU,YAAA,CAAA,CAAA,CAAA;AAAA;EAAA,OAAA,IAAAD,CAAA,CAAA,IAAA,EAAA,QAAA,CAAA,EAAA,MAAA,EAAA;AAEXE,EAAAA,WAAAA,CAAYC,KAAY,EAAE7B,IAAuB,EAAE;AACjD,IAAA,KAAK,CAAC6B,KAAA,EAAO7B,IAAA,CAAA;IAEb8B,QAAA,CAASC,gBAAgB,CAAC,SAAA,EAAW,IAAI,CAACC,YAAY,CAAA;IAEtDC,kBAAA,CAAmB,IAAI,EAAE,MAAA;MACvBH,QAAA,CAASI,mBAAmB,CAAC,SAAA,EAAW,IAAI,CAACF,YAAY,CAAA;AAC3D,IAAA,CAAA,CAAA;AACF,EAAA;EAEA,IAAIG,WAAAA,GAAc;AAChB,IAAA,OAAO,IAAI,CAACnC,IAAI,CAACmC,WAAW,IAAI,QAAA;AAClC,EAAA;EAEA,IAAIC,WAAAA,GAAc;IAChB,MAAMC,QAAQ,IAAI,CAACrC,IAAI,CAACsC,KAAK,EAAEC,MAAA,IAAU,CAAA;IACzC,MAAMC,SAAS,IAAI,CAACxC,IAAI,CAACoC,WAAW,GAAGC,KAAA,CAAA;IAEvC,OAAOG,MAAA,KAAWH,KAAA,KAAU,CAAA,GAAI,aAAa,CAAA,EAAGA,KAAA,CAAA,QAAA,CAAe,CAAA;AACjE,EAAA;AAEA;;;;AAIC;EACD,IAAII,QAAAA,GAAW;AACb,IAAA,OAAO,IAAI,CAACzC,IAAI,CAACsC,KAAK,KAAKrB,SAAA;AAC7B,EAAA;AAEA;;;;;;AAMC;AACD,EAAA,OAAOyB,CAACC,MAAmB,EAAElE,KAAY,EAAA;AACvC,IAAA,IAAI,IAAI,CAACuB,IAAI,CAACsC,KAAK,KAAKrB,SAAA,EAAW;AACjC,MAAA,IAAI,CAACjB,IAAI,CAAC4C,QAAQ,GAAGnE,KAAA,CAAA;AAErB,MAAA;AACF,IAAA;AAEA,IAAA,MAAMoE,KAAA,GAAQ,IAAI,CAAC,SAAS,CAACF,MAAA,EAAQ,IAAI,CAAC3C,IAAI,CAACsC,KAAK,CAAA;AAEpDQ,IAAAA,MAAA,CACE,2EACAD,KAAA,KAAU5B,SAAA,CAAA;IAGZ,IAAI,CAACjB,IAAI,CAAC4C,QAAQ,GAAGC,KAAA,EAAOpE,KAAA,CAAA;AAC9B,EAAA;AAEA;;;AAGC;EACD,IAAIsE,SAAAA,GAAY;AACdD,IAAAA,MAAA,CACE,kIAAA,EACA,KAAA,CAAA;AAGF,IAAA,OAAO,EAAA;AACT,EAAA;EAEA,IAAIE,KAAAA,GAAQ;AACV,IAAA,OAAO,IAAI,CAACC,MAAM,IAAI,EAAA;AACxB,EAAA;EACA,IAAID,KAAAA,CAAME,KAAa,EAAE;IACvB,IAAI,CAACD,MAAM,GAAGC,KAAA;AAChB,EAAA;AAEAC,EAAAA,YAAA,GAAeC,QAAA,CAAWC,OAAS,IAAA;AACjC,IAAA,IAAI,CAAC,KAAK,GAAGA,OAAA;AACf,EAAA,CAAA,CAAA;EAEA,IAAI,cAAcC,GAAA;IAChB,MAAM;AAAErD,MAAAA;AAAQ,KAAE,GAAG,IAAI;AAEzB,IAAA,IAAI,CAACA,UAAU,OAAOgB,SAAA;AAEtB;AACA,IAAA,OAAO,IAAI,CAAC,KAAK,EAAEsC,aAAA,CAA2B,CAAA,KAAA,EAAQtD,QAAA,CAAA,EAAA,CAAY,CAAA,IAAKgB,SAAA;AACzE,EAAA;AAEA;;;;;;AAMC;EACD,KAAKuC,CAACC,SAA6C,EAAA;AACjD,IAAA,MAAMC,SAAA,GAAY,IAAI,CAAC,KAAK;AAE5B,IAAA,IAAI,CAACA,WAAW,OAAOzC,SAAA;AAEvB,IAAA,MAAM0C,UAAUC,UAAA,CAAWC,MAAA,CAAA;IAE3Bf,MAAA,CACE,iEAAA,GACE,oFACA,kCAAA,EACFa,OAAA,CAAA;AAGF,IAAA,MAAMG,OAAA,GAAU;MAAEJ,SAAA;AAAWK,MAAAA,gCAAA,EAAkC;KAAK;AACpE,IAAA,MAAMC,cAAA,GAAiB,IAAI,CAAC,cAAc;IAC1C,MAAM;AAAEC,MAAAA;AAAS,KAAE,GAAGN,OAAA;IAEtB,IAAIF,SAAA,KAAc,OAAA,EAAS,OAAOQ,SAAA,CAAUC,SAAS,CAACJ,OAAA,CAAA;IACtD,IAAIL,SAAA,KAAc,MAAA,EAAQ,OAAOQ,SAAA,CAAUE,QAAQ,CAACL,OAAA,CAAA;IAEpD,IAAI,CAACE,cAAA,EAAgB;AACnB,MAAA,OAAOP,SAAA,KAAc,SAASQ,SAAA,CAAUC,SAAS,CAACJ,OAAA,CAAA,GAAWG,SAAA,CAAUE,QAAQ,CAACL,OAAA,CAAA;AAClF,IAAA;IAEA,MAAMM,KAAA,GACJX,SAAA,KAAc,MAAA,GACVQ,SAAA,CAAUI,QAAQ,CAAC;AAAE,MAAA,GAAGP,OAAO;AAAEE,MAAAA;AAAe,KAAA,CAAA,GAChDC,SAAA,CAAUK,QAAQ,CAAC;AAAE,MAAA,GAAGR,OAAO;AAAEE,MAAAA;AAAe,KAAA,CAAA;AAEtD;AACA,IAAA,OACEI,KAAA,KAAUX,SAAA,KAAc,MAAA,GAASQ,SAAA,CAAUC,SAAS,CAACJ,OAAA,CAAA,GAAWG,SAAA,CAAUE,QAAQ,CAACL,QAAQ,CAC7F;AACF,EAAA;EAEA,SAASS,CAAClB,OAAuC,EAAA;IAC/C,IAAI,CAACA,OAAA,EAAS;AAEd,IAAA,IAAI,CAACpD,QAAQ,GAAGoD,OAAA,CAAQxD,EAAE;IAC1BwD,OAAA,CAAQmB,cAAc,CAAC;AAAEC,MAAAA,KAAA,EAAO;AAAU,KAAA,CAAA;AAC5C,EAAA;EAEAC,QAAA,GAAY1B,KAAa,IAAA;IACvB,IAAI,CAACA,KAAK,GAAGA,KAAA;AACb;IACA,IAAI,CAAC/C,QAAQ,GAAGgB,SAAA;AAChB,IAAA,IAAI,CAACjB,IAAI,CAAC2E,aAAa,GAAG3B,KAAA,CAAA;EAC5B,CAAA;EAEA4B,WAAA,GAAenG,KAAO,IAAA;IACpB,MAAM;AAAEoG,MAAAA;AAAM,KAAE,GAAGpG,KAAA;AAEnBqE,IAAAA,MAAA,CAAO,sCAAsC+B,MAAA,YAAkBC,gBAAA,CAAA;AAE/D,IAAA,IAAI,CAACJ,QAAQ,CAACG,MAAA,CAAO3B,KAAK,CAAA;EAC5B,CAAA;EAEA6B,iBAAA,GAAqBtG,KAAO,IAAA;IAC1B,MAAM;AAAEoG,MAAAA;AAAM,KAAE,GAAGpG,KAAA;AAEnB,IAAA,IAAI,EAAEoG,MAAA,YAAkBG,OAAO,CAAA,EAAG;AAElC,IAAA,MAAMrC,MAAA,GAASkC,MAAA,CAAOI,OAAO,CAAc9G,MAAA,CAAA;AAE3C,IAAA,IAAIwE,MAAA,EAAQ;AACV,MAAA,IAAI,CAAC1C,QAAQ,GAAG0C,MAAA,CAAO9C,EAAE;AAC3B,IAAA;EACF,CAAA;EAEAqF,aAAA,GAAiBzG,KAAO,IAAA;AACtB;IACA,IAAIA,KAAA,CAAM0G,WAAW,EAAE;IAEvB,QAAQ1G,MAAMQ,GAAG;AACf,MAAA,KAAK,WAAA;AAAa,QAAA;UAChBR,KAAA,CAAM2G,cAAc,EAAA;UACpB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,MAAA,CAAA,CAAA;AAE1B,UAAA;AACF,QAAA;AACA,MAAA,KAAK,SAAA;AAAW,QAAA;UACd3G,KAAA,CAAM2G,cAAc,EAAA;UACpB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,MAAA,CAAA,CAAA;AAE1B,UAAA;AACF,QAAA;AACA,MAAA,KAAK,OAAA;AAAS,QAAA;AACZ;AACA;AACA,UAAA,MAAMC,MAAA,GAAS,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,KAAK,CAAC,OAAA,CAAA;UAEjD,IAAI,CAACA,MAAA,EAAQ;UAEb5G,KAAA,CAAM2G,cAAc,EAAA;AACpB;AACA;UACAC,MAAA,CAAOC,KAAK,EAAA;AAEZ,UAAA;AACF,QAAA;AAKF;EACF,CAAA;EAEAtD,YAAA,GAAgBvD,KAAO,IAAA;IACrB,MAAM;MAAEC,MAAM;AAAE6G,MAAAA;KAAQ,GAAG,IAAI,CAACvF,IAAI;AAEpC,IAAA,IAAI,CAACtB,MAAA,IAAU,CAAC6G,MAAA,EAAQ;AACxB,IAAA,IAAI,CAAC/G,OAAA,CAAQC,KAAA,EAAOC,MAAA,CAAA,EAAS;IAE7BD,KAAA,CAAM2G,cAAc,EAAA;AACpBG,IAAAA,MAAA,EAAA;EACF,CAAA;AAEA;;;;;AAKC;AACD,EAAA,SAASC,CAAC7C,MAAmB,EAAEL,KAAoB,EAAA;IACjD,MAAMwB,UAAU,IAAI,CAAC,KAAK,EAAE2B,gBAAA,CAA8BtH,MAAA,CAAA;AAE1D,IAAA,KAAK,IAAIuD,IAAI,CAAA,EAAGA,CAAA,IAAKoC,OAAA,EAASvB,MAAA,IAAU,CAAC,CAAA,EAAGb,CAAA,EAAA,EAAK;MAC/C,IAAIoC,OAAA,GAAUpC,CAAA,CAAE,KAAKiB,QAAQ,OAAOL,KAAK,CAACZ,CAAA,CAAE;AAC9C,IAAA;AAEA,IAAA,OAAOT,SAAA;AACT,EAAA;AAEA;;;;EAIAyE,WAAA,GAAejH,KAAO,IAAA;IACpB,MAAM;AAAEoG,MAAAA;AAAM,KAAE,GAAGpG,KAAA;AAEnB,IAAA,IAAI,EAAEoG,MAAA,YAAkBG,OAAO,CAAA,EAAG;AAElC,IAAA,MAAMrC,MAAA,GAASkC,MAAA,CAAOI,OAAO,CAAc9G,MAAA,CAAA;IAE3C,IAAI,CAACwE,MAAA,EAAQ;AAEb;;;AAGC;AACD,IAAA,IAAIA,MAAA,CAAOsC,OAAO,CAAC,GAAA,CAAA,EAAM;AACvB,MAAA,IAAIxG,KAAA,CAAMc,OAAO,IAAId,KAAA,CAAMe,OAAO,IAAIf,KAAA,CAAMiB,QAAQ,IAAIjB,KAAA,CAAMgB,MAAM,EAAE;AACtE,MAAA,IAAIhB,KAAA,CAAMkH,MAAM,KAAK,CAAA,EAAG;AAC1B,IAAA;AAEA,IAAA,IAAI,CAAC,OAAO,CAAChD,MAAA,EAAQlE,KAAA,CAAA;EACvB,CAAA;AAEA,EAAA;IAAAyB,oBAAA,CAAAC,kBAAA,CAAA,06CAAA,EAmDA;MAAAC,UAAA,EAAA,IAAA;AAAAE,MAAAA,KAAA,EAAAA,OAAA;QAAAM,KAAA;QAAAJ,IAAA;QAAAb,IAAA;QAAAU,QAAA;QAAAc,MAAA;QAAAN,OAAA;QAAAG,aAAA;QAAA4E,cAAA;AAAAlF,QAAAA;AAAA,OAAA;KAAU,CAAA,EAAV,IAAW,CAAA;AAAD;AACZ;;;;"}
|
|
@@ -10,7 +10,7 @@ import { setComponentTemplate } from '@ember/component';
|
|
|
10
10
|
import templateOnly from '@ember/component/template-only';
|
|
11
11
|
import { g, i } from 'decorator-transforms/runtime';
|
|
12
12
|
|
|
13
|
-
const DialogElement = setComponentTemplate(precompileTemplate("<dialog ...attributes
|
|
13
|
+
const DialogElement = setComponentTemplate(precompileTemplate("<dialog ...attributes {{on \"close\" @onClose}} {{@register open=@open}}>\n {{yield}}\n</dialog>", {
|
|
14
14
|
strictMode: true,
|
|
15
15
|
scope: () => ({
|
|
16
16
|
on
|
|
@@ -52,7 +52,14 @@ class ModalDialog extends Component {
|
|
|
52
52
|
g(this.prototype, "dialogElement", [tracked]);
|
|
53
53
|
}
|
|
54
54
|
#dialogElement = (i(this, "dialogElement"), void 0);
|
|
55
|
-
register = modifier(element
|
|
55
|
+
register = modifier((element, _, {
|
|
56
|
+
open
|
|
57
|
+
}) => {
|
|
58
|
+
/** The `open` attribute shows a non-modal dialog, only `showModal` is modal. */if (open && !element.open) {
|
|
59
|
+
element.showModal();
|
|
60
|
+
} else if (open === false && element.open) {
|
|
61
|
+
element.close();
|
|
62
|
+
}
|
|
56
63
|
/**
|
|
57
64
|
* This is very sad.
|
|
58
65
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog.js","sources":["../../src/components/dialog.gts"],"sourcesContent":["import Component from \"@glimmer/component\";\nimport { tracked } from \"@glimmer/tracking\";\nimport { assert } from \"@ember/debug\";\nimport { hash } from \"@ember/helper\";\nimport { on } from \"@ember/modifier\";\n\nimport { modifier as eModifier } from \"ember-modifier\";\n// temp\n// https://github.com/tracked-tools/tracked-toolbox/issues/38\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-expect-error\nimport { localCopy } from \"tracked-toolbox\";\n\nimport type { TOC } from \"@ember/component/template-only\";\nimport type { ModifierLike, WithBoundArgs } from \"@glint/template\";\n\nconst DialogElement: TOC<{\n Element: HTMLDialogElement;\n Args: {\n /**\n * @internal\n */\n open: boolean | undefined;\n /**\n * @internal\n */\n onClose: () => void;\n\n /**\n * @internal\n */\n register: ModifierLike<{ Element: HTMLDialogElement }>;\n };\n Blocks: { default: [] };\n}> = <template>\n <dialog ...attributes open={{@open}} {{on \"close\" @onClose}} {{@register}}>\n {{yield}}\n </dialog>\n</template>;\n\nexport interface Signature {\n Args: {\n /**\n * Optionally set the open state of the `<dialog>`\n * The state will still be managed internally,\n * so this does not need to be a maintained value, but whenever it changes,\n * the dialog element will reflect that change accordingly.\n */\n open?: boolean;\n /**\n * When the `<dialog>` is closed, this function will be called\n * and the `<dialog>`'s `returnValue` will be passed.\n *\n * This can be used to determine which button was clicked to close the modal\n *\n * Note though that this value is only populated when using\n * `<form method='dialog'>`\n */\n onClose?: (returnValue: string) => void;\n };\n Blocks: {\n default: [\n {\n /**\n * Represents the open state of the `<dialog>` element.\n */\n isOpen: boolean;\n\n /**\n * Closes the `<dialog>` element\n * Will throw an error if `Dialog` is not rendered.\n */\n close: () => void;\n\n /**\n * Opens the `<dialog>` element.\n * Will throw an error if `Dialog` is not rendered.\n */\n open: () => void;\n\n /**\n * This modifier should be applied to the button that opens the Dialog so that it can be re-focused when the dialog closes.\n *\n * Example:\n *\n * ```gjs\n * <template>\n * <Modal as |m|>\n * <button {{m.focusOnClose}} {{on \"click\" m.open}}>Open</button>\n *\n * <m.Dialog>...</m.Dialog>\n * </Modal>\n * </template>\n * ```\n */\n focusOnClose: ModifierLike<{ Element: HTMLElement }>;\n\n /**\n * This is the `<dialog>` element (with some defaults pre-wired).\n * This is required to be rendered.\n */\n Dialog: WithBoundArgs<typeof DialogElement, \"onClose\" | \"register\" | \"open\">;\n },\n ];\n };\n}\n\nclass ModalDialog extends Component<Signature> {\n <template>\n {{yield\n (hash\n isOpen=this.isOpen\n open=this.open\n close=this.close\n focusOnClose=this.refocus\n Dialog=(component DialogElement open=@open onClose=this.handleClose register=this.register)\n )\n }}\n </template>\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n @localCopy(\"args.open\") declare _isOpen: boolean;\n\n get isOpen() {\n /**\n * Always fallback to false (closed)\n */\n return this._isOpen ?? false;\n }\n set isOpen(val: boolean) {\n this._isOpen = val;\n }\n\n #lastIsOpen = false;\n refocus = eModifier((element) => {\n assert(`focusOnClose is only valid on a HTMLElement`, element instanceof HTMLElement);\n\n if (!this.isOpen && this.#lastIsOpen) {\n element.focus();\n }\n\n this.#lastIsOpen = this.isOpen;\n });\n\n @tracked declare dialogElement: HTMLDialogElement | undefined;\n\n register = eModifier((element: HTMLDialogElement) => {\n /**\n * This is very sad.\n *\n * But we need the element to be 'root state'\n * so that when we read things like \"isOpen\",\n * when the dialog is finally rendered, all the\n * downstream properties render.\n *\n * This has to be an async / delayed a bit, so that\n * the tracking frame can exit, and we don't infinite loop\n */\n void (async () => {\n await Promise.resolve();\n\n this.dialogElement = element;\n })();\n });\n\n /**\n * Closes the dialog -- this will throw an error in development if the dialog element was not rendered\n */\n close = () => {\n assert(\n \"Cannot call `close` on <Dialog> without rendering the dialog element.\",\n this.dialogElement,\n );\n\n /**\n * If the element is already closed, don't run all this again\n */\n if (!this.dialogElement.hasAttribute(\"open\")) {\n return;\n }\n\n /**\n * removes the `open` attribute\n * handleClose will be called because the dialog has bound the `close` event.\n */\n this.dialogElement.close();\n };\n\n /**\n * @internal\n *\n * handles the <dialog> element's native close behavior.\n * listened to via addEventListener('close', ...);\n */\n handleClose = () => {\n assert(\n \"Cannot call `handleDialogClose` on <Dialog> without rendering the dialog element. This is likely a bug in ember-primitives. Please open an issue <3\",\n this.dialogElement,\n );\n\n this.isOpen = false;\n this.args.onClose?.(this.dialogElement.returnValue);\n // the return value ends up staying... which is annoying\n this.dialogElement.returnValue = \"\";\n };\n\n /**\n * Opens the dialog -- this will throw an error in development if the dialog element was not rendered\n */\n open = () => {\n assert(\n \"Cannot call `open` on <Dialog> without rendering the dialog element.\",\n this.dialogElement,\n );\n\n /**\n * If the element is already open, don't run all this again\n */\n if (this.dialogElement.hasAttribute(\"open\")) {\n return;\n }\n\n /**\n * adds the `open` attribute\n */\n this.dialogElement.showModal();\n this.isOpen = true;\n };\n}\n\nexport interface SimpleSignature {\n Element: HTMLDialogElement;\n Blocks: {\n default: [\n {\n /**\n * Opens the dialog, modally.\n */\n open: () => void;\n /**\n * Closes the dialog.\n */\n close: () => void;\n },\n ];\n };\n}\n\n/**\n * A modal `<dialog>` around whatever you put in it, and the two things needed\n * to drive it.\n *\n * ```gjs\n * <Dialog as |d|>\n * <CommandPalette @onSelect={{d.close}} @onOpen={{d.open}} @hotkey=\"mod+k\" />\n * </Dialog>\n * ```\n *\n * The element is here, so a trigger cannot be: whatever opens this either\n * lives inside it, or is a key combination. For a dialog opened by a button\n * beside it, use `<Modal>`, which hands you the element to place.\n *\n * <kbd>Escape</kbd> closes it and focus returns to whatever opened it, which\n * is the `<dialog>` element's own behaviour. Set `closedby` to change which\n * actions dismiss it.\n */\nexport class Dialog extends Component<SimpleSignature> {\n /**\n * A plain field rather than tracked state: it is read when somebody calls\n * `open` or `close`, never while rendering, so nothing has to settle in a\n * second pass.\n */\n #element: HTMLDialogElement | undefined;\n\n register = eModifier((element: HTMLDialogElement) => {\n this.#element = element;\n\n return () => {\n this.#element = undefined;\n };\n });\n\n /**\n * `showModal` on an open dialog, and `close` on a closed one, are both\n * no-ops per spec, so neither needs guarding here.\n */\n open = () => {\n this.#element?.showModal();\n };\n\n close = () => {\n this.#element?.close();\n };\n\n <template>\n <dialog {{this.register}} ...attributes>\n {{yield (hash open=this.open close=this.close)}}\n </dialog>\n </template>\n}\n\nexport const Modal = ModalDialog;\n\nexport default ModalDialog;\n"],"names":["DialogElement","setComponentTemplate","precompileTemplate","strictMode","scope","on","templateOnly","ModalDialog","Component","hash","g","prototype","localCopy","i","isOpen","_isOpen","val","refocus","eModifier","element","assert","HTMLElement","focus","tracked","register","Promise","resolve","dialogElement","close","hasAttribute","handleClose","args","onClose","returnValue","open","showModal","Dialog","undefined","Modal"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dialog.js","sources":["../../src/components/dialog.gts"],"sourcesContent":["import Component from \"@glimmer/component\";\nimport { tracked } from \"@glimmer/tracking\";\nimport { assert } from \"@ember/debug\";\nimport { hash } from \"@ember/helper\";\nimport { on } from \"@ember/modifier\";\n\nimport { modifier as eModifier } from \"ember-modifier\";\n// temp\n// https://github.com/tracked-tools/tracked-toolbox/issues/38\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-expect-error\nimport { localCopy } from \"tracked-toolbox\";\n\nimport type { TOC } from \"@ember/component/template-only\";\nimport type { ModifierLike, WithBoundArgs } from \"@glint/template\";\n\ninterface RegisterSignature {\n Element: HTMLDialogElement;\n Args: { Named: { open: boolean | undefined } };\n}\n\nconst DialogElement: TOC<{\n Element: HTMLDialogElement;\n Args: {\n /**\n * @internal\n */\n open: boolean | undefined;\n /**\n * @internal\n */\n onClose: () => void;\n\n /**\n * @internal\n */\n register: ModifierLike<RegisterSignature>;\n };\n Blocks: { default: [] };\n}> = <template>\n <dialog ...attributes {{on \"close\" @onClose}} {{@register open=@open}}>\n {{yield}}\n </dialog>\n</template>;\n\nexport interface Signature {\n Args: {\n /**\n * Optionally set the open state of the `<dialog>`\n * The state will still be managed internally,\n * so this does not need to be a maintained value, but whenever it changes,\n * the dialog element will reflect that change accordingly.\n */\n open?: boolean;\n /**\n * When the `<dialog>` is closed, this function will be called\n * and the `<dialog>`'s `returnValue` will be passed.\n *\n * This can be used to determine which button was clicked to close the modal\n *\n * Note though that this value is only populated when using\n * `<form method='dialog'>`\n */\n onClose?: (returnValue: string) => void;\n };\n Blocks: {\n default: [\n {\n /**\n * Represents the open state of the `<dialog>` element.\n */\n isOpen: boolean;\n\n /**\n * Closes the `<dialog>` element\n * Will throw an error if `Dialog` is not rendered.\n */\n close: () => void;\n\n /**\n * Opens the `<dialog>` element.\n * Will throw an error if `Dialog` is not rendered.\n */\n open: () => void;\n\n /**\n * This modifier should be applied to the button that opens the Dialog so that it can be re-focused when the dialog closes.\n *\n * Example:\n *\n * ```gjs\n * <template>\n * <Modal as |m|>\n * <button {{m.focusOnClose}} {{on \"click\" m.open}}>Open</button>\n *\n * <m.Dialog>...</m.Dialog>\n * </Modal>\n * </template>\n * ```\n */\n focusOnClose: ModifierLike<{ Element: HTMLElement }>;\n\n /**\n * This is the `<dialog>` element (with some defaults pre-wired).\n * This is required to be rendered.\n */\n Dialog: WithBoundArgs<typeof DialogElement, \"onClose\" | \"register\" | \"open\">;\n },\n ];\n };\n}\n\nclass ModalDialog extends Component<Signature> {\n <template>\n {{yield\n (hash\n isOpen=this.isOpen\n open=this.open\n close=this.close\n focusOnClose=this.refocus\n Dialog=(component DialogElement open=@open onClose=this.handleClose register=this.register)\n )\n }}\n </template>\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n @localCopy(\"args.open\") declare _isOpen: boolean;\n\n get isOpen() {\n /**\n * Always fallback to false (closed)\n */\n return this._isOpen ?? false;\n }\n set isOpen(val: boolean) {\n this._isOpen = val;\n }\n\n #lastIsOpen = false;\n refocus = eModifier((element) => {\n assert(`focusOnClose is only valid on a HTMLElement`, element instanceof HTMLElement);\n\n if (!this.isOpen && this.#lastIsOpen) {\n element.focus();\n }\n\n this.#lastIsOpen = this.isOpen;\n });\n\n @tracked declare dialogElement: HTMLDialogElement | undefined;\n\n register = eModifier<RegisterSignature>((element, _, { open }) => {\n /** The `open` attribute shows a non-modal dialog, only `showModal` is modal. */\n if (open && !element.open) {\n element.showModal();\n } else if (open === false && element.open) {\n element.close();\n }\n\n /**\n * This is very sad.\n *\n * But we need the element to be 'root state'\n * so that when we read things like \"isOpen\",\n * when the dialog is finally rendered, all the\n * downstream properties render.\n *\n * This has to be an async / delayed a bit, so that\n * the tracking frame can exit, and we don't infinite loop\n */\n void (async () => {\n await Promise.resolve();\n\n this.dialogElement = element;\n })();\n });\n\n /**\n * Closes the dialog -- this will throw an error in development if the dialog element was not rendered\n */\n close = () => {\n assert(\n \"Cannot call `close` on <Dialog> without rendering the dialog element.\",\n this.dialogElement,\n );\n\n /**\n * If the element is already closed, don't run all this again\n */\n if (!this.dialogElement.hasAttribute(\"open\")) {\n return;\n }\n\n /**\n * removes the `open` attribute\n * handleClose will be called because the dialog has bound the `close` event.\n */\n this.dialogElement.close();\n };\n\n /**\n * @internal\n *\n * handles the <dialog> element's native close behavior.\n * listened to via addEventListener('close', ...);\n */\n handleClose = () => {\n assert(\n \"Cannot call `handleDialogClose` on <Dialog> without rendering the dialog element. This is likely a bug in ember-primitives. Please open an issue <3\",\n this.dialogElement,\n );\n\n this.isOpen = false;\n this.args.onClose?.(this.dialogElement.returnValue);\n // the return value ends up staying... which is annoying\n this.dialogElement.returnValue = \"\";\n };\n\n /**\n * Opens the dialog -- this will throw an error in development if the dialog element was not rendered\n */\n open = () => {\n assert(\n \"Cannot call `open` on <Dialog> without rendering the dialog element.\",\n this.dialogElement,\n );\n\n /**\n * If the element is already open, don't run all this again\n */\n if (this.dialogElement.hasAttribute(\"open\")) {\n return;\n }\n\n /**\n * adds the `open` attribute\n */\n this.dialogElement.showModal();\n this.isOpen = true;\n };\n}\n\nexport interface SimpleSignature {\n Element: HTMLDialogElement;\n Blocks: {\n default: [\n {\n /**\n * Opens the dialog, modally.\n */\n open: () => void;\n /**\n * Closes the dialog.\n */\n close: () => void;\n },\n ];\n };\n}\n\n/**\n * A modal `<dialog>` around whatever you put in it, and the two things needed\n * to drive it.\n *\n * ```gjs\n * <Dialog as |d|>\n * <CommandPalette @onSelect={{d.close}} @onOpen={{d.open}} @hotkey=\"mod+k\" />\n * </Dialog>\n * ```\n *\n * The element is here, so a trigger cannot be: whatever opens this either\n * lives inside it, or is a key combination. For a dialog opened by a button\n * beside it, use `<Modal>`, which hands you the element to place.\n *\n * <kbd>Escape</kbd> closes it and focus returns to whatever opened it, which\n * is the `<dialog>` element's own behaviour. Set `closedby` to change which\n * actions dismiss it.\n */\nexport class Dialog extends Component<SimpleSignature> {\n /**\n * A plain field rather than tracked state: it is read when somebody calls\n * `open` or `close`, never while rendering, so nothing has to settle in a\n * second pass.\n */\n #element: HTMLDialogElement | undefined;\n\n register = eModifier((element: HTMLDialogElement) => {\n this.#element = element;\n\n return () => {\n this.#element = undefined;\n };\n });\n\n /**\n * `showModal` on an open dialog, and `close` on a closed one, are both\n * no-ops per spec, so neither needs guarding here.\n */\n open = () => {\n this.#element?.showModal();\n };\n\n close = () => {\n this.#element?.close();\n };\n\n <template>\n <dialog {{this.register}} ...attributes>\n {{yield (hash open=this.open close=this.close)}}\n </dialog>\n </template>\n}\n\nexport const Modal = ModalDialog;\n\nexport default ModalDialog;\n"],"names":["DialogElement","setComponentTemplate","precompileTemplate","strictMode","scope","on","templateOnly","ModalDialog","Component","hash","g","prototype","localCopy","i","isOpen","_isOpen","val","refocus","eModifier","element","assert","HTMLElement","focus","tracked","register","_","open","showModal","close","Promise","resolve","dialogElement","hasAttribute","handleClose","args","onClose","returnValue","Dialog","undefined","Modal"],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@ import { setComponentTemplate } from '@ember/component';
|
|
|
10
10
|
import templateOnly from '@ember/component/template-only';
|
|
11
11
|
import { g, i } from 'decorator-transforms/runtime';
|
|
12
12
|
|
|
13
|
-
const DrawerElement = setComponentTemplate(precompileTemplate("<dialog ...attributes
|
|
13
|
+
const DrawerElement = setComponentTemplate(precompileTemplate("<dialog ...attributes {{on \"close\" @onClose}} {{@register open=@open}}>\n {{yield}}\n</dialog>", {
|
|
14
14
|
strictMode: true,
|
|
15
15
|
scope: () => ({
|
|
16
16
|
on
|
|
@@ -52,7 +52,14 @@ class DrawerDialog extends Component {
|
|
|
52
52
|
g(this.prototype, "drawerElement", [tracked]);
|
|
53
53
|
}
|
|
54
54
|
#drawerElement = (i(this, "drawerElement"), void 0);
|
|
55
|
-
register = modifier(element
|
|
55
|
+
register = modifier((element, _, {
|
|
56
|
+
open
|
|
57
|
+
}) => {
|
|
58
|
+
/** The `open` attribute shows a non-modal drawer, only `showModal` is modal. */if (open && !element.open) {
|
|
59
|
+
element.showModal();
|
|
60
|
+
} else if (open === false && element.open) {
|
|
61
|
+
element.close();
|
|
62
|
+
}
|
|
56
63
|
/**
|
|
57
64
|
* This is very sad.
|
|
58
65
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drawer.js","sources":["../../src/components/drawer.gts"],"sourcesContent":["import Component from \"@glimmer/component\";\nimport { tracked } from \"@glimmer/tracking\";\nimport { assert } from \"@ember/debug\";\nimport { hash } from \"@ember/helper\";\nimport { on } from \"@ember/modifier\";\n\nimport { modifier as eModifier } from \"ember-modifier\";\n// temp\n// https://github.com/tracked-tools/tracked-toolbox/issues/38\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-expect-error\nimport { localCopy } from \"tracked-toolbox\";\n\nimport type { TOC } from \"@ember/component/template-only\";\nimport type { ModifierLike, WithBoundArgs } from \"@glint/template\";\n\nconst DrawerElement: TOC<{\n Element: HTMLDialogElement;\n Args: {\n /**\n * @internal\n */\n open: boolean | undefined;\n /**\n * @internal\n */\n onClose: () => void;\n\n /**\n * @internal\n */\n register: ModifierLike<
|
|
1
|
+
{"version":3,"file":"drawer.js","sources":["../../src/components/drawer.gts"],"sourcesContent":["import Component from \"@glimmer/component\";\nimport { tracked } from \"@glimmer/tracking\";\nimport { assert } from \"@ember/debug\";\nimport { hash } from \"@ember/helper\";\nimport { on } from \"@ember/modifier\";\n\nimport { modifier as eModifier } from \"ember-modifier\";\n// temp\n// https://github.com/tracked-tools/tracked-toolbox/issues/38\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-expect-error\nimport { localCopy } from \"tracked-toolbox\";\n\nimport type { TOC } from \"@ember/component/template-only\";\nimport type { ModifierLike, WithBoundArgs } from \"@glint/template\";\n\ninterface RegisterSignature {\n Element: HTMLDialogElement;\n Args: { Named: { open: boolean | undefined } };\n}\n\nconst DrawerElement: TOC<{\n Element: HTMLDialogElement;\n Args: {\n /**\n * @internal\n */\n open: boolean | undefined;\n /**\n * @internal\n */\n onClose: () => void;\n\n /**\n * @internal\n */\n register: ModifierLike<RegisterSignature>;\n };\n Blocks: { default: [] };\n}> = <template>\n <dialog ...attributes {{on \"close\" @onClose}} {{@register open=@open}}>\n {{yield}}\n </dialog>\n</template>;\n\nexport interface Signature {\n Args: {\n /**\n * Optionally set the open state of the drawer\n * The state will still be managed internally,\n * so this does not need to be a maintained value, but whenever it changes,\n * the drawer element will reflect that change accordingly.\n */\n open?: boolean;\n /**\n * When the drawer is closed, this function will be called\n * and the drawer's `returnValue` will be passed.\n *\n * This can be used to determine which button was clicked to close the drawer\n *\n * Note though that this value is only populated when using\n * `<form method='dialog'>`\n */\n onClose?: (returnValue: string) => void;\n };\n Blocks: {\n default: [\n {\n /**\n * Represents the open state of the drawer element.\n */\n isOpen: boolean;\n\n /**\n * Closes the drawer element\n * Will throw an error if `Drawer` is not rendered.\n */\n close: () => void;\n\n /**\n * Opens the drawer element.\n * Will throw an error if `Drawer` is not rendered.\n */\n open: () => void;\n\n /**\n * This modifier should be applied to the button that opens the Drawer so that it can be re-focused when the drawer closes.\n *\n * Example:\n *\n * ```gjs\n * <template>\n * <Drawer as |d|>\n * <button {{d.focusOnClose}} {{on \"click\" d.open}}>Open</button>\n *\n * <d.Drawer>...</d.Drawer>\n * </Drawer>\n * </template>\n * ```\n */\n focusOnClose: ModifierLike<{ Element: HTMLElement }>;\n\n /**\n * This is the `<dialog>` element (with some defaults pre-wired).\n * This is required to be rendered.\n */\n Drawer: WithBoundArgs<typeof DrawerElement, \"onClose\" | \"register\" | \"open\">;\n },\n ];\n };\n}\n\nclass DrawerDialog extends Component<Signature> {\n <template>\n {{yield\n (hash\n isOpen=this.isOpen\n open=this.open\n close=this.close\n focusOnClose=this.refocus\n Drawer=(component DrawerElement open=@open onClose=this.handleClose register=this.register)\n )\n }}\n </template>\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n @localCopy(\"args.open\") declare _isOpen: boolean;\n\n get isOpen() {\n /**\n * Always fallback to false (closed)\n */\n return this._isOpen ?? false;\n }\n set isOpen(val: boolean) {\n this._isOpen = val;\n }\n\n #lastIsOpen = false;\n refocus = eModifier((element) => {\n assert(`focusOnClose is only valid on a HTMLElement`, element instanceof HTMLElement);\n\n if (!this.isOpen && this.#lastIsOpen) {\n element.focus();\n }\n\n this.#lastIsOpen = this.isOpen;\n });\n\n @tracked declare drawerElement: HTMLDialogElement | undefined;\n\n register = eModifier<RegisterSignature>((element, _, { open }) => {\n /** The `open` attribute shows a non-modal drawer, only `showModal` is modal. */\n if (open && !element.open) {\n element.showModal();\n } else if (open === false && element.open) {\n element.close();\n }\n\n /**\n * This is very sad.\n *\n * But we need the element to be 'root state'\n * so that when we read things like \"isOpen\",\n * when the drawer is finally rendered, all the\n * downstream properties render.\n *\n * This has to be an async / delayed a bit, so that\n * the tracking frame can exit, and we don't infinite loop\n */\n void (async () => {\n await Promise.resolve();\n\n this.drawerElement = element;\n })();\n });\n\n /**\n * Closes the drawer -- this will throw an error in development if the drawer element was not rendered\n */\n close = () => {\n assert(\n \"Cannot call `close` on <Drawer> without rendering the drawer element.\",\n this.drawerElement,\n );\n\n /**\n * If the element is already closed, don't run all this again\n */\n if (!this.drawerElement.hasAttribute(\"open\")) {\n return;\n }\n\n /**\n * removes the `open` attribute\n * handleClose will be called because the drawer has bound the `close` event.\n */\n this.drawerElement.close();\n };\n\n /**\n * @internal\n *\n * handles the <dialog> element's native close behavior.\n * listened to via addEventListener('close', ...);\n */\n handleClose = () => {\n assert(\n \"Cannot call `handleClose` on <Drawer> without rendering the drawer element. This is likely a bug in ember-primitives. Please open an issue <3\",\n this.drawerElement,\n );\n\n this.isOpen = false;\n this.args.onClose?.(this.drawerElement.returnValue);\n // the return value ends up staying... which is annoying\n this.drawerElement.returnValue = \"\";\n };\n\n /**\n * Opens the drawer -- this will throw an error in development if the drawer element was not rendered\n */\n open = () => {\n assert(\n \"Cannot call `open` on <Drawer> without rendering the drawer element.\",\n this.drawerElement,\n );\n\n /**\n * If the element is already open, don't run all this again\n */\n if (this.drawerElement.hasAttribute(\"open\")) {\n return;\n }\n\n /**\n * adds the `open` attribute\n */\n this.drawerElement.showModal();\n this.isOpen = true;\n };\n}\n\nexport const Drawer = DrawerDialog;\n\nexport default DrawerDialog;\n"],"names":["DrawerElement","setComponentTemplate","precompileTemplate","strictMode","scope","on","templateOnly","DrawerDialog","Component","hash","g","prototype","localCopy","i","isOpen","_isOpen","val","refocus","eModifier","element","assert","HTMLElement","focus","tracked","register","_","open","showModal","close","Promise","resolve","drawerElement","hasAttribute","handleClose","args","onClose","returnValue","Drawer"],"mappings":"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"}
|
package/dist/components/link.js
CHANGED
|
@@ -18,7 +18,7 @@ import templateOnly from '@ember/component/template-only';
|
|
|
18
18
|
*
|
|
19
19
|
* [mdn-a]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a
|
|
20
20
|
*/
|
|
21
|
-
const Link = setComponentTemplate(precompileTemplate("{{#let (link @href includeActiveQueryParams=@includeActiveQueryParams activeOnSubPaths=@activeOnSubPaths) as |l|}}\n {{#if l.isExternal}}\n <ExternalLink href={{@href}} ...attributes>\n {{yield (hash isExternal=true isActive=false)}}\n </ExternalLink>\n {{else}}\n <a data-active={{l.isActive}} href={{if @href @href \"##missing##\"}} {{on \"click\" l.handleClick}} ...attributes>\n {{yield (hash isExternal=false isActive=l.isActive)}}\n </a>\n {{/if}}\n{{/let}}", {
|
|
21
|
+
const Link = setComponentTemplate(precompileTemplate("{{#let (link @href includeActiveQueryParams=@includeActiveQueryParams activeOnSubPaths=@activeOnSubPaths) as |l|}}\n {{#if l.isExternal}}\n <ExternalLink href={{@href}} ...attributes>\n {{yield (hash isExternal=true isActive=false)}}\n </ExternalLink>\n {{else}}\n <a data-active={{l.isActive}} aria-current={{if l.isActive (if @activeOnSubPaths \"true\" \"page\")}} href={{if @href @href \"##missing##\"}} {{on \"click\" l.handleClick}} ...attributes>\n {{yield (hash isExternal=false isActive=l.isActive)}}\n </a>\n {{/if}}\n{{/let}}", {
|
|
22
22
|
strictMode: true,
|
|
23
23
|
scope: () => ({
|
|
24
24
|
link,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link.js","sources":["../../src/components/link.gts"],"sourcesContent":["/**\n * TODO: make template-only component,\n * and use class-based modifier?\n *\n * This would require that modifiers could run pre-render\n */\nimport { hash } from '@ember/helper';\nimport { on } from '@ember/modifier';\n\nimport { link } from '../helpers/link.ts';\nimport { ExternalLink } from './external-link.gts';\n\nimport type { TOC } from '@ember/component/template-only';\n\nexport interface Signature {\n Element: HTMLAnchorElement;\n Args: {\n /**\n * the `href` string value to set on the anchor element.\n */\n href: string;\n /**\n * When calculating the \"active\" state of the link, you may decide\n * whether or not you want to _require_ that all query params be considered (true)\n * or specify individual query params, ignoring anything not specified.\n *\n * For example:\n *\n * ```gjs live preview\n * import { Link } from 'ember-primitives';\n *\n * <template>\n * <Link @href=\"/\" @includeActiveQueryParams={{true}} as |a|>\n * ...\n * </Link>\n * </template>\n * ```\n *\n * the data-active state here will only be \"true\" on\n * - `/`\n * - `/?foo=2`\n * - `/?foo=&bar=`\n *\n */\n includeActiveQueryParams?: true | string[];\n /**\n * When calculating the \"active\" state of the link, you may decide\n * whether or not you want to consider sub paths to be active when\n * child routes/urls are active.\n *\n * For example:\n *\n * ```gjs live preview\n * import { Link } from 'ember-primitives';\n *\n * <template>\n * <Link @href=\"/forum/1\" @activeOnSubPaths={{true}} as |a|>\n * ...\n * </Link>\n * </template>\n * ```\n *\n * the data-active state here will be \"true\" on\n * - `/forum/1`\n * - `/forum/1/posts`\n * - `/forum/1/posts/comments`\n * - `/forum/1/*etc*`\n *\n * if `@activeOnSubPaths` is set to false or left off\n * the data-active state here will only be \"true\" on\n * - `/forum/1`\n *\n */\n activeOnSubPaths?: true;\n };\n Blocks: {\n default: [\n {\n /**\n * Indicates if the passed `href` is pointing to an external site.\n * Useful if you want your links to have additional context for when\n * a user is about to leave your site.\n *\n * For example:\n *\n * ```gjs live preview\n * import { Link } from 'ember-primitives';\n *\n * const MyLink = <template>\n * <Link @href={{@href}} as |a|>\n * {{yield}}\n * {{#if a.isExternal}}\n * ➚\n * {{/if}}\n * </Link>\n * </template>;\n *\n * <template>\n * <MyLink @href=\"https://developer.mozilla.org\">MDN</MyLink> \n * <MyLink @href=\"/\">Home</MyLink>\n * </template>\n * ```\n */\n isExternal: boolean;\n /**\n * Indicates if the passed `href` is *active*, or the user is on the same basepath.\n * This allows consumers to style their link if they wish or style their text.\n * The active state will also be present on a `data-active` attribute on the generated anchor tag.\n *\n *\n * For example\n * ```gjs\n * import { Link, service } from 'ember-primitives';\n *\n * const MyLink = <template>\n * <Link @href=\"...\"> as |a|>\n * <span class=\"{{if a.isActive 'underline'}}\">\n * {{yield}}\n * </span>\n * </Link>\n * </template>\n *\n * <template>\n * {{#let (service 'router') as |router|}}\n * <MyLink @href={{router.currentURL}}>Ths page</MyLink> \n * <MyLink @href=\"/\">Home</MyLink>\n * {{/let}}\n * </template>\n * ```\n *\n * By default, the query params are omitted from `isActive` calculation, but you may\n * configure the query params to be included if you wish\n * See: `@includeActiveQueryParams`\n *\n * By default, only the exact route/url is considered for the `isActive` calculation,\n * but you may configure sub routes/paths to also be considered active\n * See: `@activeOnSubPaths`\n *\n * Note that external links are never active.\n */\n isActive: boolean;\n },\n ];\n };\n}\n\n/**\n * A light wrapper around the [Anchor element][mdn-a], which will appropriately make your link an external link if the passed `@href` is not on the same domain.\n *\n *\n * [mdn-a]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a\n */\nexport const Link: TOC<Signature> = <template>\n {{#let (link @href includeActiveQueryParams=@includeActiveQueryParams activeOnSubPaths=@activeOnSubPaths) as |l|}}\n {{#if l.isExternal}}\n <ExternalLink href={{@href}} ...attributes>\n {{yield (hash isExternal=true isActive=false)}}\n </ExternalLink>\n {{else}}\n <a\n data-active={{l.isActive}}\n href={{if @href @href \"##missing##\"}}\n {{on \"click\" l.handleClick}}\n ...attributes\n >\n {{yield (hash isExternal=false isActive=l.isActive)}}\n </a>\n {{/if}}\n {{/let}}\n</template>;\n\nexport default Link;\n"],"names":["Link","setComponentTemplate","precompileTemplate","strictMode","scope","link","ExternalLink","hash","on","templateOnly"],"mappings":";;;;;;;;AAAA;;;;;AAKC;AA6ID;;;;;AAKC;MACYA,IAAU,GAAAC,oBAAA,CAAaC,kBAAA,CAAA,
|
|
1
|
+
{"version":3,"file":"link.js","sources":["../../src/components/link.gts"],"sourcesContent":["/**\n * TODO: make template-only component,\n * and use class-based modifier?\n *\n * This would require that modifiers could run pre-render\n */\nimport { hash } from '@ember/helper';\nimport { on } from '@ember/modifier';\n\nimport { link } from '../helpers/link.ts';\nimport { ExternalLink } from './external-link.gts';\n\nimport type { TOC } from '@ember/component/template-only';\n\nexport interface Signature {\n Element: HTMLAnchorElement;\n Args: {\n /**\n * the `href` string value to set on the anchor element.\n */\n href: string;\n /**\n * When calculating the \"active\" state of the link, you may decide\n * whether or not you want to _require_ that all query params be considered (true)\n * or specify individual query params, ignoring anything not specified.\n *\n * For example:\n *\n * ```gjs live preview\n * import { Link } from 'ember-primitives';\n *\n * <template>\n * <Link @href=\"/\" @includeActiveQueryParams={{true}} as |a|>\n * ...\n * </Link>\n * </template>\n * ```\n *\n * the data-active state here will only be \"true\" on\n * - `/`\n * - `/?foo=2`\n * - `/?foo=&bar=`\n *\n */\n includeActiveQueryParams?: true | string[];\n /**\n * When calculating the \"active\" state of the link, you may decide\n * whether or not you want to consider sub paths to be active when\n * child routes/urls are active.\n *\n * For example:\n *\n * ```gjs live preview\n * import { Link } from 'ember-primitives';\n *\n * <template>\n * <Link @href=\"/forum/1\" @activeOnSubPaths={{true}} as |a|>\n * ...\n * </Link>\n * </template>\n * ```\n *\n * the data-active state here will be \"true\" on\n * - `/forum/1`\n * - `/forum/1/posts`\n * - `/forum/1/posts/comments`\n * - `/forum/1/*etc*`\n *\n * if `@activeOnSubPaths` is set to false or left off\n * the data-active state here will only be \"true\" on\n * - `/forum/1`\n *\n */\n activeOnSubPaths?: true;\n };\n Blocks: {\n default: [\n {\n /**\n * Indicates if the passed `href` is pointing to an external site.\n * Useful if you want your links to have additional context for when\n * a user is about to leave your site.\n *\n * For example:\n *\n * ```gjs live preview\n * import { Link } from 'ember-primitives';\n *\n * const MyLink = <template>\n * <Link @href={{@href}} as |a|>\n * {{yield}}\n * {{#if a.isExternal}}\n * ➚\n * {{/if}}\n * </Link>\n * </template>;\n *\n * <template>\n * <MyLink @href=\"https://developer.mozilla.org\">MDN</MyLink> \n * <MyLink @href=\"/\">Home</MyLink>\n * </template>\n * ```\n */\n isExternal: boolean;\n /**\n * Indicates if the passed `href` is *active*, or the user is on the same basepath.\n * This allows consumers to style their link if they wish or style their text.\n * The active state will also be present on a `data-active` attribute on the generated anchor tag.\n *\n *\n * For example\n * ```gjs\n * import { Link, service } from 'ember-primitives';\n *\n * const MyLink = <template>\n * <Link @href=\"...\"> as |a|>\n * <span class=\"{{if a.isActive 'underline'}}\">\n * {{yield}}\n * </span>\n * </Link>\n * </template>\n *\n * <template>\n * {{#let (service 'router') as |router|}}\n * <MyLink @href={{router.currentURL}}>Ths page</MyLink> \n * <MyLink @href=\"/\">Home</MyLink>\n * {{/let}}\n * </template>\n * ```\n *\n * By default, the query params are omitted from `isActive` calculation, but you may\n * configure the query params to be included if you wish\n * See: `@includeActiveQueryParams`\n *\n * By default, only the exact route/url is considered for the `isActive` calculation,\n * but you may configure sub routes/paths to also be considered active\n * See: `@activeOnSubPaths`\n *\n * Note that external links are never active.\n */\n isActive: boolean;\n },\n ];\n };\n}\n\n/**\n * A light wrapper around the [Anchor element][mdn-a], which will appropriately make your link an external link if the passed `@href` is not on the same domain.\n *\n *\n * [mdn-a]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a\n */\nexport const Link: TOC<Signature> = <template>\n {{#let (link @href includeActiveQueryParams=@includeActiveQueryParams activeOnSubPaths=@activeOnSubPaths) as |l|}}\n {{#if l.isExternal}}\n <ExternalLink href={{@href}} ...attributes>\n {{yield (hash isExternal=true isActive=false)}}\n </ExternalLink>\n {{else}}\n <a\n data-active={{l.isActive}}\n aria-current={{if l.isActive (if @activeOnSubPaths \"true\" \"page\")}}\n href={{if @href @href \"##missing##\"}}\n {{on \"click\" l.handleClick}}\n ...attributes\n >\n {{yield (hash isExternal=false isActive=l.isActive)}}\n </a>\n {{/if}}\n {{/let}}\n</template>;\n\nexport default Link;\n"],"names":["Link","setComponentTemplate","precompileTemplate","strictMode","scope","link","ExternalLink","hash","on","templateOnly"],"mappings":";;;;;;;;AAAA;;;;;AAKC;AA6ID;;;;;AAKC;MACYA,IAAU,GAAAC,oBAAA,CAAaC,kBAAA,CAAA,kjBAAA,EAkBpC;EAAAC,UAAA,EAAA,IAAA;AAAAC,EAAAA,KAAA,EAAAA,OAAA;IAAAC,IAAA;IAAAC,YAAA;IAAAC,IAAA;AAAAC,IAAAA;AAAA,GAAA;AAAU,CAAA,CAAA,EAAAC,YAAA,EAAA;;;;"}
|
package/dist/components/menu.js
CHANGED
|
@@ -81,6 +81,7 @@ const installContent = modifier((element, _, {
|
|
|
81
81
|
// listen for the escape key
|
|
82
82
|
function onDocumentKeydown(e) {
|
|
83
83
|
if (isOpen.current && e.key === "Escape") {
|
|
84
|
+
/** Otherwise the same key press also closes a `<dialog>` around the menu. */e.preventDefault();
|
|
84
85
|
isOpen.current = false;
|
|
85
86
|
}
|
|
86
87
|
}
|
|
@@ -92,7 +93,7 @@ const installContent = modifier((element, _, {
|
|
|
92
93
|
document.removeEventListener("keydown", onDocumentKeydown);
|
|
93
94
|
};
|
|
94
95
|
});
|
|
95
|
-
const Content = setComponentTemplate(precompileTemplate("{{#if @isOpen.current}}\n <@PopoverContent id={{@contentId}} role=\"menu\" data-tabster={{TABSTER_CONFIG_CONTENT}} tabindex=\"0\" {{installContent isOpen=@isOpen triggerElement=@triggerElement}} ...attributes>\n {{yield (hash Item=(component Item toggle=@isOpen.toggle) LinkItem=(component LinkItem toggle=@isOpen.toggle) Separator=Separator)}}\n </@PopoverContent>\n{{/if}}", {
|
|
96
|
+
const Content = setComponentTemplate(precompileTemplate("{{#if @isOpen.current}}\n <@PopoverContent id={{@contentId}} role=\"menu\" aria-labelledby={{@triggerElement.current.id}} data-tabster={{TABSTER_CONFIG_CONTENT}} tabindex=\"0\" {{installContent isOpen=@isOpen triggerElement=@triggerElement}} ...attributes>\n {{yield (hash Item=(component Item toggle=@isOpen.toggle) LinkItem=(component LinkItem toggle=@isOpen.toggle) Separator=Separator)}}\n </@PopoverContent>\n{{/if}}", {
|
|
96
97
|
strictMode: true,
|
|
97
98
|
scope: () => ({
|
|
98
99
|
TABSTER_CONFIG_CONTENT,
|
|
@@ -130,6 +131,9 @@ const trigger = modifier((element, _, {
|
|
|
130
131
|
isOpen.toggle();
|
|
131
132
|
};
|
|
132
133
|
element.addEventListener("click", onTriggerClick);
|
|
134
|
+
if (!element.id) {
|
|
135
|
+
element.id = `${contentId}__trigger`;
|
|
136
|
+
}
|
|
133
137
|
triggerElement.current = element;
|
|
134
138
|
setReference(element);
|
|
135
139
|
return () => {
|