@blockquote-web-components/blockquote-base-embedded-webview 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,142 @@
1
+ import { html, LitElement } from 'lit';
2
+ import styles from './styles/BlockquoteBaseEmbeddedWebviewElement-styles.js';
3
+ /**
4
+ ![Lit](https://img.shields.io/badge/lit-2.0.0-blue)
5
+
6
+ `blockquote-base-embedded-webview-element` wraps an `iframe` or `object` and shows it through light dom.
7
+
8
+ ## Exports
9
+
10
+ - BlockquoteBaseEmbeddedWebviewElement
11
+
12
+ @tagname blockquote-base-embedded-webview-element
13
+ */
14
+ export class BlockquoteBaseEmbeddedWebviewElement extends LitElement {
15
+ static get is() {
16
+ return 'blockquote-base-embedded-webview-element';
17
+ }
18
+
19
+ static get styles() {
20
+ return [styles];
21
+ }
22
+
23
+ static get properties() {
24
+ return {
25
+ /**
26
+ * The title attribute on an <element> to label its content
27
+ * @type {string}
28
+ */
29
+ embeddedTitle: {
30
+ type: String,
31
+ attribute: 'embedded-title',
32
+ },
33
+
34
+ /**
35
+ * The URL of the page to embed
36
+ * @type {string}
37
+ */
38
+ src: {
39
+ type: String,
40
+ },
41
+
42
+ /**
43
+ * The type of the tag to embed - iframe or object
44
+ * @type {string}
45
+ */
46
+ type: {
47
+ type: String,
48
+ },
49
+ };
50
+ }
51
+
52
+ constructor() {
53
+ super();
54
+ this.embeddedTitle = '';
55
+ this.src = '';
56
+ this.type = 'iframe';
57
+ this._onLoadElement = this._onLoadElement.bind(this);
58
+ }
59
+
60
+ connectedCallback() {
61
+ if (this._embeddedElement) {
62
+ return;
63
+ }
64
+ super.connectedCallback && super.connectedCallback();
65
+
66
+ this._embeddedElement = document.createElement(this.type);
67
+
68
+ Object.assign(this._embeddedElement, {
69
+ slot: 'embedded',
70
+ });
71
+
72
+ this._embeddedElement.addEventListener('load', this._onLoadElement);
73
+
74
+ // append element after add listener `load`
75
+ this.append(this._embeddedElement);
76
+ }
77
+
78
+ willUpdate(props) {
79
+ super.willUpdate && super.willUpdate(props);
80
+
81
+ if (props.has('src') && this.src !== '') {
82
+ this._fetch(this.src);
83
+ }
84
+ }
85
+
86
+ render() {
87
+ return html` ${this._embeddedTpl} `;
88
+ }
89
+
90
+ get _loadResource() {
91
+ return this.type === 'iframe' ? 'src' : 'data';
92
+ }
93
+
94
+ get _embeddedTpl() {
95
+ return html`<slot name="embedded"></slot>`;
96
+ }
97
+
98
+ _fetch(resource) {
99
+ if (resource) {
100
+ if (this.embeddedTitle) {
101
+ this._embeddedElement.title = this.embeddedTitle;
102
+ }
103
+ Object.assign(
104
+ this._embeddedElement,
105
+ this.type === 'iframe' && {
106
+ allowFullscreen: true,
107
+ allow: 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture,',
108
+ },
109
+ );
110
+
111
+ this._embeddedElement[this._loadResource] = resource;
112
+
113
+ Object.assign(
114
+ this._embeddedElement.style,
115
+ resource.indexOf('http') !== 0 && {
116
+ opacity: 0,
117
+ },
118
+ );
119
+ }
120
+ }
121
+
122
+ _onLoadElement({ target }) {
123
+ if (!target.contentDocument || !target.contentDocument.head.childNodes.length) {
124
+ return;
125
+ }
126
+ Object.assign(target.contentDocument.body.dataset, {
127
+ embedded: '',
128
+ });
129
+ window.requestAnimationFrame(() => target.removeAttribute('style'));
130
+
131
+ /**
132
+ * Raised when the `resource` is load.
133
+ *
134
+ * @event elementloaded
135
+ */
136
+ const event = new CustomEvent('elementloaded', {
137
+ bubbles: true,
138
+ detail: target,
139
+ });
140
+ this.dispatchEvent(event);
141
+ }
142
+ }
@@ -0,0 +1,165 @@
1
+ import { html, LitElement } from 'lit';
2
+ import * as Gestures from '@blockquote/polymer/lib/utils/gestures.js';
3
+ import styles from './styles/BlockquoteBaseEmbeddedWebviewResize-styles.js';
4
+ // https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect#value - window.scrollY to get a bounding rectangle which is independent from the current scrolling position.
5
+ // https://www.browserstack.com/guide/ideal-screen-sizes-for-responsive-design
6
+ // https://stackoverflow.com/questions/26233180/resize-a-div-on-border-drag-and-drop-without-adding-extra-markup
7
+ // https://github.com/ChromeDevTools/devtools-frontend/blob/main/front_end/Images/src/resizeHorizontal.svg
8
+
9
+ /**
10
+ ![Lit](https://img.shields.io/badge/lit-2.0.0-blue)
11
+
12
+ `blockquote-base-embedded-webview-resize`
13
+
14
+ ## Exports
15
+
16
+ - BlockquoteBaseEmbeddedWebviewResize
17
+
18
+ @tagname blockquote-base-embedded-webview-resize
19
+ */
20
+ export class BlockquoteBaseEmbeddedWebviewResize extends LitElement {
21
+ static get is() {
22
+ return 'blockquote-base-embedded-webview-resize';
23
+ }
24
+
25
+ static get styles() {
26
+ return [styles];
27
+ }
28
+
29
+ constructor() {
30
+ super();
31
+ this._resizer = this._resizer.bind(this);
32
+ this._removeResizer = this._removeResizer.bind(this);
33
+ this._createResizerLeft = this._createResizer.bind(this, 'right');
34
+ this._createResizerRight = this._createResizer.bind(this, 'left');
35
+ this._createResizerBottom = this._createResizer.bind(this, 'top');
36
+ this._createResizerBottomLeft = this._createResizer.bind(this, 'scaleTopRight');
37
+ this._createResizerBottomRight = this._createResizer.bind(this, 'scaleTopLeft');
38
+ this._doubleclickForCssInitialSize = this._doubleclickForCssInitialSize.bind(this);
39
+ }
40
+
41
+ firstUpdated(props) {
42
+ super.firstUpdated && super.firstUpdated(props);
43
+
44
+ this.rect = this.shadowRoot.querySelector('.rect');
45
+ this.bottomRightResizerElement = this.shadowRoot.querySelector('.resizer-se');
46
+ this.bottomLeftResizerElement = this.shadowRoot.querySelector('.resizer-sw');
47
+ this.rightResizerElement = this.shadowRoot.querySelector('.resizer-e');
48
+ this.leftResizerElement = this.shadowRoot.querySelector('.resizer-w');
49
+ this.bottomResizerElement = this.shadowRoot.querySelector('.resizer-s');
50
+
51
+ this.leftResizerElement.addEventListener('mousedown', this._createResizerLeft);
52
+ this.rightResizerElement.addEventListener('mousedown', this._createResizerRight);
53
+ this.bottomResizerElement.addEventListener('mousedown', this._createResizerBottom);
54
+ this.bottomLeftResizerElement.addEventListener('mousedown', this._createResizerBottomLeft);
55
+ this.bottomRightResizerElement.addEventListener('mousedown', this._createResizerBottomRight);
56
+
57
+ this.bottomResizerElement.addEventListener('dblclick', this._doubleclickForCssInitialSize);
58
+ }
59
+
60
+ render() {
61
+ return html`
62
+ <div class="rect">
63
+ ${this._resizersTpl}
64
+ <slot></slot>
65
+ </div>
66
+ `;
67
+ }
68
+
69
+ get _resizersTpl() {
70
+ return html`
71
+ <span aria-hidden="true" class="resizer resizer-n"></span>
72
+ <span aria-hidden="true" class="resizer resizer-e"></span>
73
+ <span aria-hidden="true" class="resizer resizer-s"></span>
74
+ <span aria-hidden="true" class="resizer resizer-w"></span>
75
+ <span aria-hidden="true" class="resizer resizer-se"></span>
76
+ <span aria-hidden="true" class="resizer resizer-sw"></span>
77
+ `;
78
+ }
79
+
80
+ _doubleclickForCssInitialSize() {
81
+ console.log('eeeeee', this);
82
+ this.removeAttribute('style');
83
+ // this._dispatchResizeEvent();
84
+ }
85
+
86
+ _createResizer(DOMRect) {
87
+ this._getBoundingClientRecDOMRect = DOMRect;
88
+ this._getBoundingClientRectWidth = this._getBoundingClientRect('width');
89
+ this._getBoundingClientRectHeight = this._getBoundingClientRect('height');
90
+
91
+ this.setAttribute('resizing', '');
92
+ Gestures.addListener(document, 'track', this._resizer);
93
+ document.addEventListener('mouseup', this._removeResizer);
94
+ }
95
+
96
+ _removeResizer() {
97
+ this.removeAttribute('resizing');
98
+ Gestures.removeListener(document, 'track', this._resizer);
99
+ document.removeEventListener('mouseup', this._removeResizer);
100
+ this._dispatchResizeEvent();
101
+ }
102
+
103
+ _resizer({ detail }) {
104
+ let cssOffsetX;
105
+ let cssOffsetY;
106
+ const dx = Math.floor(detail.dx * 2);
107
+ const dy = Math.floor(detail.dy * 2);
108
+
109
+ switch (this._getBoundingClientRecDOMRect) {
110
+ case 'right':
111
+ cssOffsetX = `${this._getBoundingClientRectWidth - dx}px`;
112
+ this.style.setProperty('--blockquote-base-embedded-webview-resize-rect-width', cssOffsetX);
113
+ break;
114
+ case 'left':
115
+ cssOffsetX = `${this._getBoundingClientRectWidth + dx}px`;
116
+ this.style.setProperty('--blockquote-base-embedded-webview-resize-rect-width', cssOffsetX);
117
+ break;
118
+ case 'top':
119
+ cssOffsetY = `${this._getBoundingClientRectHeight + dy * 1}px`;
120
+ this.style.setProperty('--blockquote-base-embedded-webview-resize-rect-height', cssOffsetY);
121
+ break;
122
+ case 'scaleTopLeft':
123
+ cssOffsetX = `${this._getBoundingClientRectWidth + dx}px`;
124
+ cssOffsetY = `${this._getBoundingClientRectHeight + dy * 1}px`;
125
+ this.style.setProperty('--blockquote-base-embedded-webview-resize-rect-width', cssOffsetX);
126
+ this.style.setProperty('--blockquote-base-embedded-webview-resize-rect-height', cssOffsetY);
127
+ break;
128
+ case 'scaleTopRight':
129
+ cssOffsetX = `${this._getBoundingClientRectWidth - dx}px`;
130
+ cssOffsetY = `${this._getBoundingClientRectHeight + dy * 1}px`;
131
+ this.style.setProperty('--blockquote-base-embedded-webview-resize-rect-width', cssOffsetX);
132
+ this.style.setProperty('--blockquote-base-embedded-webview-resize-rect-height', cssOffsetY);
133
+ break;
134
+ /* c8 ignore next */
135
+ default:
136
+ }
137
+
138
+ this._dispatchResizeEvent();
139
+ }
140
+
141
+ _dispatchResizeEvent() {
142
+ /**
143
+ * Raised when the element's dimensions changes.
144
+ *
145
+ * @event webviewresize
146
+ */
147
+ const event = new CustomEvent('webviewresize', {
148
+ bubbles: true,
149
+ detail: {
150
+ x: getComputedStyle(this).getPropertyValue(
151
+ '--blockquote-base-embedded-webview-resize-rect-width',
152
+ ),
153
+ y: getComputedStyle(this).getPropertyValue(
154
+ '--blockquote-base-embedded-webview-resize-rect-height',
155
+ ),
156
+ resizing: this.hasAttribute('resizing'),
157
+ },
158
+ });
159
+ this.dispatchEvent(event);
160
+ }
161
+
162
+ _getBoundingClientRect(DOMRect) {
163
+ return Math.abs(parseInt(this.rect.getBoundingClientRect()[DOMRect], 10));
164
+ }
165
+ }
@@ -0,0 +1,207 @@
1
+ import { html, LitElement } from 'lit';
2
+ import styles from './styles/BlockquoteBaseEmbeddedWebviewSize-styles.js';
3
+ // https://source.chromium.org/chromium/chromium/src/+/main:third_party/devtools-frontend/src/front_end/panels/emulation/DeviceModeView.ts;l=164
4
+
5
+ /**
6
+ ![Lit](https://img.shields.io/badge/lit-2.0.0-blue)
7
+
8
+ `blockquote-base-embedded-webview-size` provides a list of ideal screen sizes for responsive designs.
9
+
10
+ ```html
11
+ <blockquote-base-embedded-webview-size
12
+ screen-sizes="[
13
+ { width: 360, height: 640, id: '360x640' },
14
+ { width: 375, height: 667, id: '375x667' },
15
+ { width: 414, height: 896, id: '414x896' },
16
+ { width: 768, height: 1024, id: '768x1024' },
17
+ { width: 1024, height: 768, id: '1024x768' },
18
+ { width: 1280, height: 800, id: '1280x800' },
19
+ { width: 1366, height: 768, id: '1366x768' },
20
+ { width: 1536, height: 864, id: '1536x864' },
21
+ { width: 1920, height: 1080, id: '1920x1080' },
22
+ ]"
23
+ ></blockquote-base-embedded-webview-size>
24
+ ```
25
+
26
+ ## Exports
27
+
28
+ - BlockquoteBaseEmbeddedWebviewSize
29
+
30
+ @tagname blockquote-base-embedded-webview-size
31
+ */
32
+ export class BlockquoteBaseEmbeddedWebviewSize extends LitElement {
33
+ static get is() {
34
+ return 'blockquote-base-embedded-webview-size';
35
+ }
36
+
37
+ static get styles() {
38
+ return [styles];
39
+ }
40
+
41
+ static get properties() {
42
+ return {
43
+ /**
44
+ * The screen size options to display
45
+ * @type {Array}
46
+ */
47
+ screenSizes: {
48
+ type: Array,
49
+ attribute: 'screen-sizes',
50
+ },
51
+ /**
52
+ * The screen size option selected
53
+ * @type {Number}
54
+ */
55
+ selected: {
56
+ type: Number,
57
+ },
58
+
59
+ /**
60
+ * Percentage value for the width
61
+ * @type {Boolean}
62
+ */
63
+ widthInPercent: {
64
+ type: Boolean,
65
+ attribute: 'width-in-percent',
66
+ },
67
+
68
+ /**
69
+ * Show screen size options that are too large for the container
70
+ * @type {Boolean}
71
+ */
72
+ showOverflowSize: {
73
+ type: Boolean,
74
+ attribute: 'show-overflow-size',
75
+ },
76
+ };
77
+ }
78
+
79
+ constructor() {
80
+ super();
81
+ this.showOverflowSize = false;
82
+ this.selected = 0;
83
+ this.screenSizes = [
84
+ { width: 360, height: 640, id: '360x640' },
85
+ { width: 375, height: 667, id: '375x667' },
86
+ { width: 414, height: 896, id: '414x896' },
87
+ { width: 768, height: 1024, id: '768x1024' },
88
+ { width: 1024, height: 768, id: '1024x768' },
89
+ { width: 1280, height: 800, id: '1280x800' },
90
+ { width: 1366, height: 768, id: '1366x768' },
91
+ { width: 1536, height: 864, id: '1536x864' },
92
+ { width: 1920, height: 1080, id: '1920x1080' },
93
+ ];
94
+ this.widthInPercent = false;
95
+
96
+ this._onResize = this._onResize.bind(this);
97
+ }
98
+
99
+ get selectedSize() {
100
+ return this.screenSizes[this.selected - 1];
101
+ }
102
+
103
+ get selectedDetail() {
104
+ return { ...this.selectedSize, ...{ index: this.selected } };
105
+ }
106
+
107
+ get computedStyleWidth() {
108
+ return parseInt(window.getComputedStyle(this).width, 10);
109
+ }
110
+
111
+ connectedCallback() {
112
+ super.connectedCallback && super.connectedCallback();
113
+ window.addEventListener('resize', this._onResize);
114
+ }
115
+
116
+ disconnectedCallback() {
117
+ super.disconnectedCallback && super.disconnectedCallback();
118
+ window.removeEventListener('resize', this._onResize);
119
+ }
120
+
121
+ willUpdate(props) {
122
+ super.willUpdate && super.willUpdate(props);
123
+
124
+ if (props.has('screenSizes')) {
125
+ this.screenSizes.sort((a, b) => b.width - a.width);
126
+ }
127
+
128
+ if (props.has('selected')) {
129
+ if (this.selected > this.screenSizes.length || this.selected === 0) {
130
+ this.selected = this.screenSizes.length;
131
+ }
132
+ }
133
+ }
134
+
135
+ updated(props) {
136
+ super.updated && super.updated(props);
137
+
138
+ if (props.has('selected')) {
139
+ /**
140
+ * Raised when the `selected` property changes.
141
+ *
142
+ * @event selectedchange
143
+ */
144
+ const event = new CustomEvent('selectedchange', {
145
+ bubbles: true,
146
+ detail: this.selectedDetail,
147
+ });
148
+ this.dispatchEvent(event);
149
+ }
150
+ }
151
+
152
+ render() {
153
+ return html`
154
+ <div class="rect">
155
+ ${this._toolbarTpl}
156
+ ${this._visualTextTpl}
157
+ </div>
158
+ </div>
159
+ `;
160
+ }
161
+
162
+ get _toolbarTpl() {
163
+ return html`
164
+ ${this.screenSizes.map(
165
+ (item, index) => html`<button
166
+ @click="${this._setSelected}"
167
+ id="${item.id}"
168
+ data-index="${index + 1}"
169
+ ?data-selected="${this.selected === index + 1}"
170
+ ?hidden="${!this.showOverflowSize && item.width > this.computedStyleWidth}"
171
+ style="${this.widthInPercent
172
+ ? `width: calc(100% / ${index + 1});`
173
+ : `width: ${item.width}px;`}"
174
+ >
175
+ <span>${item.id}</span>
176
+ </button>`,
177
+ )}
178
+ `;
179
+ }
180
+
181
+ get _visualTextTpl() {
182
+ return html` <span aria-hidden="true">${this.selectedSize.id}</span>`;
183
+ }
184
+
185
+ _onResize(ev) {
186
+ ev.preventDefault();
187
+ ev.stopPropagation();
188
+ window.requestAnimationFrame(() => {
189
+ this.requestUpdate();
190
+ });
191
+ }
192
+
193
+ _setSelected(ev) {
194
+ ev.preventDefault();
195
+ ev.stopPropagation();
196
+ this.selected = Number(ev.target.dataset.index);
197
+
198
+ /**
199
+ * Fired `click` event.
200
+ * @event click
201
+ */
202
+ const event = new CustomEvent('click', {
203
+ detail: this.selectedDetail,
204
+ });
205
+ this.dispatchEvent(event);
206
+ }
207
+ }
@@ -0,0 +1,104 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import { css } from 'lit';
3
+
4
+ export default css`:host {
5
+ --_host-color: var(--blockquote-base-embedded-webview-color, rgb(32, 33, 36));
6
+ --_main-bgcolor: var(--blockquote-base-embedded-webview-main-bgcolor, rgb(248, 249, 249));
7
+ --_select-bgcolor: var(--blockquote-base-embedded-webview-select-bgcolor, rgb(222, 225, 230));
8
+ --_select-transition: var(--blockquote-base-embedded-webview-select-transition, border-bottom 196ms ease-out, var(--blockquote-base-embedded-webview-select-transition, border-bottom 196ms ease-out));
9
+ --blockquote-base-embedded-webview-resize-rect-width: 640px; /* 40rem */
10
+ --blockquote-base-embedded-webview-resize-rect-height: 360px; /* 22.5rem */
11
+ display: flex;
12
+ flex-direction: column;
13
+ box-sizing: border-box;
14
+ width: 100%;
15
+ height: 100%;
16
+ color: var(--_host-color);
17
+ }
18
+
19
+ :host([hidden]),
20
+ [hidden] {
21
+ display: none !important;
22
+ }
23
+
24
+ .main {
25
+ flex: 1;
26
+ background-color: var(--_main-bgcolor);
27
+ }
28
+
29
+ :host([limit-height]) .main {
30
+ height: inherit;
31
+ }
32
+
33
+ *,
34
+ *::before,
35
+ *::after {
36
+ box-sizing: inherit;
37
+ }
38
+
39
+ [role=heading] {
40
+ font-size: 1.25rem;
41
+ margin-bottom: 0.5rem;
42
+ }
43
+
44
+ header > div {
45
+ position: relative;
46
+ max-width: 80rem;
47
+ margin: 0 auto;
48
+ padding: 0.5rem 1.5rem;
49
+ }
50
+
51
+ .select {
52
+ display: inline-grid;
53
+ grid-template-areas: select;
54
+ align-items: center;
55
+ margin-bottom: 0.5rem;
56
+ }
57
+ .select > * {
58
+ grid-area: select;
59
+ }
60
+ .select svg {
61
+ width: 0.875rem;
62
+ justify-self: end;
63
+ pointer-events: none;
64
+ }
65
+ .select select {
66
+ -webkit-appearance: none;
67
+ -moz-appearance: none;
68
+ appearance: none;
69
+ color: inherit;
70
+ font: inherit;
71
+ font-size: 0.875rem;
72
+ background-color: transparent;
73
+ border: none;
74
+ border-bottom: 0.125rem solid var(--_select-bgcolor);
75
+ padding: 0.2857142857em 1.2857142857em 0.2857142857em 0;
76
+ margin: 0;
77
+ width: 100%;
78
+ cursor: pointer;
79
+ outline: none;
80
+ border-radius: 0;
81
+ min-width: 10ch;
82
+ max-width: 18ch;
83
+ white-space: nowrap;
84
+ overflow: hidden;
85
+ text-overflow: ellipsis;
86
+ transition: var(--_select-transition);
87
+ }
88
+ .select select:hover, .select select:focus {
89
+ border-bottom-color: currentcolor;
90
+ }
91
+
92
+ .read-data-pos {
93
+ font-size: 0.875rem;
94
+ letter-spacing: 0.0156rem;
95
+ position: absolute;
96
+ right: 0.375rem;
97
+ top: 0.3125rem;
98
+ opacity: 0;
99
+ transition: opacity 90ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
100
+ }
101
+
102
+ blockquote-base-embedded-webview-resize {
103
+ overflow-x: hidden;
104
+ }`;
@@ -0,0 +1,22 @@
1
+ import { css } from 'lit';
2
+
3
+ export default css`:host,
4
+ ::slotted([slot=embedded]) {
5
+ display: block;
6
+ box-sizing: border-box;
7
+ width: 100%;
8
+ height: 100%;
9
+ margin: 0;
10
+ border: 0;
11
+ }
12
+
13
+ :host([hidden]),
14
+ [hidden] {
15
+ display: none !important;
16
+ }
17
+
18
+ *,
19
+ *::before,
20
+ *::after {
21
+ box-sizing: inherit;
22
+ }`;