@aglyn/shared-ui-jsx 1.0.0-beta.233 → 1.0.0-beta.235

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.
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * Preview and view links open in a NEW TAB (AGL-3660).
19
+ *
20
+ * A "Preview", "View live", "Visit site" or eye-icon action shows the reader
21
+ * something ELSE — a rendered artifact, a published page, a buyer's view —
22
+ * while they are in the middle of working on a list or an editor. Sending the
23
+ * console tab there threw away their place (filters, scroll, an open drawer)
24
+ * for a glance they meant to take beside it. So every such action opens a tab
25
+ * of its own, and says so to assistive tech.
26
+ *
27
+ * This module is the one place that spelling lives:
28
+ *
29
+ * - {@link newTabLinkProps} for an anchor / `AppLink` / MUI `href` control;
30
+ * - {@link openInNewTab} for a click handler that has the URL in hand;
31
+ * - {@link openPendingTab} for one that must `await` the URL first — a tab
32
+ * opened after the await is a popup the browser blocks;
33
+ * - {@link withNewTabHint} for the accessible name.
34
+ *
35
+ * `apps/console/specs/preview-links-open-in-new-tab.spec.ts` sweeps the
36
+ * console and plugin sources and fails when a preview/view action is wired
37
+ * without one of these (or an equivalent `target="_blank"`).
38
+ */
39
+ /** The suffix every new-tab control's accessible name ends with. */
40
+ export declare const NEW_TAB_HINT = "(opens in a new tab)";
41
+ /**
42
+ * `noopener` so the opened page cannot script this console through
43
+ * `window.opener`; `noreferrer` so a published site does not learn the
44
+ * console URL (org slug, site id) it was opened from.
45
+ */
46
+ export declare const NEW_TAB_REL = "noopener noreferrer";
47
+ /** Spread onto an anchor-rendering control: `<AppLink {...newTabLinkProps}>`. */
48
+ export declare const newTabLinkProps: Readonly<{
49
+ readonly target: "_blank";
50
+ readonly rel: "noopener noreferrer";
51
+ }>;
52
+ /**
53
+ * `rel` for a new-tab link that already carries tokens of its own
54
+ * (`nofollow`, say): theirs, plus `noopener noreferrer`, each once.
55
+ */
56
+ export declare function mergeNewTabRel(rel?: string | null): string;
57
+ /** `label` with {@link NEW_TAB_HINT} appended, exactly once. */
58
+ export declare function withNewTabHint(label: string): string;
59
+ /**
60
+ * Open `url` in a new tab from a click handler. Use this rather than
61
+ * `router.push` / `location.assign` for a preview or view action.
62
+ */
63
+ export declare function openInNewTab(url: string): void;
64
+ /** A tab opened during the click, navigated once its URL is known. */
65
+ export interface PendingTab {
66
+ /** Send the tab to `url`; falls back to {@link openInNewTab} if the browser
67
+ * refused the blank tab. Never navigates the console tab itself. */
68
+ navigate(url: string): void;
69
+ /** Close the blank tab, for when the URL could not be produced. */
70
+ close(): void;
71
+ }
72
+ /**
73
+ * Open a blank tab NOW, inside the click's user activation, and point it at a
74
+ * URL that is only known after an `await`.
75
+ *
76
+ * NOT `window.open('', '_blank', 'noopener')`: with `noopener` the browser
77
+ * returns `null`, so there is no handle to navigate — the pattern that left
78
+ * "Preview on site" opening an empty tab AND moving the console tab. Instead
79
+ * the tab is opened with a handle and its `opener` is cut by hand before it
80
+ * goes anywhere, which is what `noopener` would have done.
81
+ */
82
+ export declare function openPendingTab(): PendingTab;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * Preview and view links open in a NEW TAB (AGL-3660).
18
+ *
19
+ * A "Preview", "View live", "Visit site" or eye-icon action shows the reader
20
+ * something ELSE — a rendered artifact, a published page, a buyer's view —
21
+ * while they are in the middle of working on a list or an editor. Sending the
22
+ * console tab there threw away their place (filters, scroll, an open drawer)
23
+ * for a glance they meant to take beside it. So every such action opens a tab
24
+ * of its own, and says so to assistive tech.
25
+ *
26
+ * This module is the one place that spelling lives:
27
+ *
28
+ * - {@link newTabLinkProps} for an anchor / `AppLink` / MUI `href` control;
29
+ * - {@link openInNewTab} for a click handler that has the URL in hand;
30
+ * - {@link openPendingTab} for one that must `await` the URL first — a tab
31
+ * opened after the await is a popup the browser blocks;
32
+ * - {@link withNewTabHint} for the accessible name.
33
+ *
34
+ * `apps/console/specs/preview-links-open-in-new-tab.spec.ts` sweeps the
35
+ * console and plugin sources and fails when a preview/view action is wired
36
+ * without one of these (or an equivalent `target="_blank"`).
37
+ */ /** The suffix every new-tab control's accessible name ends with. */ export const NEW_TAB_HINT = '(opens in a new tab)';
38
+ /**
39
+ * `noopener` so the opened page cannot script this console through
40
+ * `window.opener`; `noreferrer` so a published site does not learn the
41
+ * console URL (org slug, site id) it was opened from.
42
+ */ export const NEW_TAB_REL = 'noopener noreferrer';
43
+ /** Spread onto an anchor-rendering control: `<AppLink {...newTabLinkProps}>`. */ export const newTabLinkProps = Object.freeze({
44
+ target: '_blank',
45
+ rel: NEW_TAB_REL
46
+ });
47
+ /**
48
+ * `rel` for a new-tab link that already carries tokens of its own
49
+ * (`nofollow`, say): theirs, plus `noopener noreferrer`, each once.
50
+ */ export function mergeNewTabRel(rel) {
51
+ const tokens = new Set(`${rel != null ? rel : ''} ${NEW_TAB_REL}`.split(/\s+/).filter(Boolean));
52
+ return [
53
+ ...tokens
54
+ ].join(' ');
55
+ }
56
+ /** `label` with {@link NEW_TAB_HINT} appended, exactly once. */ export function withNewTabHint(label) {
57
+ const trimmed = label.trim();
58
+ return trimmed.endsWith(NEW_TAB_HINT) ? trimmed : `${trimmed} ${NEW_TAB_HINT}`;
59
+ }
60
+ /**
61
+ * Open `url` in a new tab from a click handler. Use this rather than
62
+ * `router.push` / `location.assign` for a preview or view action.
63
+ */ export function openInNewTab(url) {
64
+ if (typeof window === 'undefined' || !url) return;
65
+ window.open(url, '_blank', 'noopener,noreferrer');
66
+ }
67
+ /**
68
+ * Open a blank tab NOW, inside the click's user activation, and point it at a
69
+ * URL that is only known after an `await`.
70
+ *
71
+ * NOT `window.open('', '_blank', 'noopener')`: with `noopener` the browser
72
+ * returns `null`, so there is no handle to navigate — the pattern that left
73
+ * "Preview on site" opening an empty tab AND moving the console tab. Instead
74
+ * the tab is opened with a handle and its `opener` is cut by hand before it
75
+ * goes anywhere, which is what `noopener` would have done.
76
+ */ export function openPendingTab() {
77
+ const tab = typeof window === 'undefined' ? null : window.open('about:blank', '_blank');
78
+ if (tab) {
79
+ try {
80
+ tab.opener = null;
81
+ } catch (unused) {
82
+ // Cross-origin by now is impossible for about:blank; nothing to cut.
83
+ }
84
+ }
85
+ return {
86
+ navigate (url) {
87
+ if (!url) return;
88
+ if (tab && !tab.closed) tab.location.replace(url);
89
+ else openInNewTab(url);
90
+ },
91
+ close () {
92
+ if (tab && !tab.closed) tab.close();
93
+ }
94
+ };
95
+ }
96
+
97
+ //# sourceMappingURL=new-tab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/utils/new-tab.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Preview and view links open in a NEW TAB (AGL-3660).\n *\n * A \"Preview\", \"View live\", \"Visit site\" or eye-icon action shows the reader\n * something ELSE — a rendered artifact, a published page, a buyer's view —\n * while they are in the middle of working on a list or an editor. Sending the\n * console tab there threw away their place (filters, scroll, an open drawer)\n * for a glance they meant to take beside it. So every such action opens a tab\n * of its own, and says so to assistive tech.\n *\n * This module is the one place that spelling lives:\n *\n * - {@link newTabLinkProps} for an anchor / `AppLink` / MUI `href` control;\n * - {@link openInNewTab} for a click handler that has the URL in hand;\n * - {@link openPendingTab} for one that must `await` the URL first — a tab\n * opened after the await is a popup the browser blocks;\n * - {@link withNewTabHint} for the accessible name.\n *\n * `apps/console/specs/preview-links-open-in-new-tab.spec.ts` sweeps the\n * console and plugin sources and fails when a preview/view action is wired\n * without one of these (or an equivalent `target=\"_blank\"`).\n */\n\n/** The suffix every new-tab control's accessible name ends with. */\nexport const NEW_TAB_HINT = '(opens in a new tab)'\n\n/**\n * `noopener` so the opened page cannot script this console through\n * `window.opener`; `noreferrer` so a published site does not learn the\n * console URL (org slug, site id) it was opened from.\n */\nexport const NEW_TAB_REL = 'noopener noreferrer'\n\n/** Spread onto an anchor-rendering control: `<AppLink {...newTabLinkProps}>`. */\nexport const newTabLinkProps = Object.freeze({\n target: '_blank',\n rel: NEW_TAB_REL,\n} as const)\n\n/**\n * `rel` for a new-tab link that already carries tokens of its own\n * (`nofollow`, say): theirs, plus `noopener noreferrer`, each once.\n */\nexport function mergeNewTabRel(rel?: string | null): string {\n const tokens = new Set(\n `${rel ?? ''} ${NEW_TAB_REL}`.split(/\\s+/).filter(Boolean),\n )\n return [...tokens].join(' ')\n}\n\n/** `label` with {@link NEW_TAB_HINT} appended, exactly once. */\nexport function withNewTabHint(label: string): string {\n const trimmed = label.trim()\n return trimmed.endsWith(NEW_TAB_HINT) ? trimmed : `${trimmed} ${NEW_TAB_HINT}`\n}\n\n/**\n * Open `url` in a new tab from a click handler. Use this rather than\n * `router.push` / `location.assign` for a preview or view action.\n */\nexport function openInNewTab(url: string): void {\n if (typeof window === 'undefined' || !url) return\n window.open(url, '_blank', 'noopener,noreferrer')\n}\n\n/** A tab opened during the click, navigated once its URL is known. */\nexport interface PendingTab {\n /** Send the tab to `url`; falls back to {@link openInNewTab} if the browser\n * refused the blank tab. Never navigates the console tab itself. */\n navigate(url: string): void\n /** Close the blank tab, for when the URL could not be produced. */\n close(): void\n}\n\n/**\n * Open a blank tab NOW, inside the click's user activation, and point it at a\n * URL that is only known after an `await`.\n *\n * NOT `window.open('', '_blank', 'noopener')`: with `noopener` the browser\n * returns `null`, so there is no handle to navigate — the pattern that left\n * \"Preview on site\" opening an empty tab AND moving the console tab. Instead\n * the tab is opened with a handle and its `opener` is cut by hand before it\n * goes anywhere, which is what `noopener` would have done.\n */\nexport function openPendingTab(): PendingTab {\n const tab =\n typeof window === 'undefined' ? null : window.open('about:blank', '_blank')\n if (tab) {\n try {\n tab.opener = null\n } catch {\n // Cross-origin by now is impossible for about:blank; nothing to cut.\n }\n }\n return {\n navigate(url: string) {\n if (!url) return\n if (tab && !tab.closed) tab.location.replace(url)\n else openInNewTab(url)\n },\n close() {\n if (tab && !tab.closed) tab.close()\n },\n }\n}\n"],"names":["NEW_TAB_HINT","NEW_TAB_REL","newTabLinkProps","Object","freeze","target","rel","mergeNewTabRel","tokens","Set","split","filter","Boolean","join","withNewTabHint","label","trimmed","trim","endsWith","openInNewTab","url","window","open","openPendingTab","tab","opener","navigate","closed","location","replace","close"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;CAqBC,GAED,kEAAkE,GAClE,OAAO,MAAMA,eAAe,uBAAsB;AAElD;;;;CAIC,GACD,OAAO,MAAMC,cAAc,sBAAqB;AAEhD,+EAA+E,GAC/E,OAAO,MAAMC,kBAAkBC,OAAOC,MAAM,CAAC;IAC3CC,QAAQ;IACRC,KAAKL;AACP,GAAW;AAEX;;;CAGC,GACD,OAAO,SAASM,eAAeD,GAAmB;IAChD,MAAME,SAAS,IAAIC,IACjB,GAAGH,cAAAA,MAAO,GAAG,CAAC,EAAEL,aAAa,CAACS,KAAK,CAAC,OAAOC,MAAM,CAACC;IAEpD,OAAO;WAAIJ;KAAO,CAACK,IAAI,CAAC;AAC1B;AAEA,8DAA8D,GAC9D,OAAO,SAASC,eAAeC,KAAa;IAC1C,MAAMC,UAAUD,MAAME,IAAI;IAC1B,OAAOD,QAAQE,QAAQ,CAAClB,gBAAgBgB,UAAU,GAAGA,QAAQ,CAAC,EAAEhB,cAAc;AAChF;AAEA;;;CAGC,GACD,OAAO,SAASmB,aAAaC,GAAW;IACtC,IAAI,OAAOC,WAAW,eAAe,CAACD,KAAK;IAC3CC,OAAOC,IAAI,CAACF,KAAK,UAAU;AAC7B;AAWA;;;;;;;;;CASC,GACD,OAAO,SAASG;IACd,MAAMC,MACJ,OAAOH,WAAW,cAAc,OAAOA,OAAOC,IAAI,CAAC,eAAe;IACpE,IAAIE,KAAK;QACP,IAAI;YACFA,IAAIC,MAAM,GAAG;QACf,EAAE,eAAM;QACN,qEAAqE;QACvE;IACF;IACA,OAAO;QACLC,UAASN,GAAW;YAClB,IAAI,CAACA,KAAK;YACV,IAAII,OAAO,CAACA,IAAIG,MAAM,EAAEH,IAAII,QAAQ,CAACC,OAAO,CAACT;iBACxCD,aAAaC;QACpB;QACAU;YACE,IAAIN,OAAO,CAACA,IAAIG,MAAM,EAAEH,IAAIM,KAAK;QACnC;IACF;AACF"}